//-----------------------------------------------------------
// Copyright Christian Arnault LAL-Orsay CNRS
// arnault@lal.in2p3.fr
// Modified by: garonne@lal.in2p3.fr
// See the complete license in cmt_license.txt "http://www.cecill.info". 
//-----------------------------------------------------------

#include <stdlib.h>
#include <stdio.h>

#include "cmt.h"
#include "cmt_cvs.h"
#include "cmt_awk.h"
#include "cmt_symbol.h"
#include "cmt_project.h"

/**

    Grep : perform a grep like operation onto a cmt_string

    o All lines of the input string are collected when they contain
      the specified pattern.
    o The input string and the selector pattern are specified in
      the constructor:

         Grep (input_string, pattern)

    o All selected lines are accumulated (appended) into the internal
      variable m_result . 'space' is the separator.

    o The accumulator is retrieved by the result () method.

 */
class Grep : public Awk
{
public:

  void begin ();
  void filter (const cmt_string& line);
  const cmt_string& result () const;

private:
  cmt_string m_result;
};

/**

     Cut : perform a cut-like operation :

     o collect the <field>'th field of every line into the m_result 
       internal variable

     o the field number is given in the constructor and starts at zero.

     o selected fields are accumulated with a space as separator.

 */
class Cut : public Awk
{
public:
  Cut (int field);
  void begin ();
  void filter (const cmt_string& line);
  const cmt_string& result () const;

private:
  cmt_string m_result;
  int m_field;
};

/**

      History : maintains the history of checkouts during a 
                recursive checkout, so as to avoid double checkouts.

 */
class History
{
public:
  static History& instance ();
  void clear ();
  void install (const cmt_string& line);
  bool is_installed (const cmt_string& line);

private:
  History ();

  cmt_string m_installed;
};

/**

     RecursivePass1 : simply validate use statements in a requirements file
                      and echo those that really need to be handled.

 */
class RecursivePass1 : public Awk
{
public:

  void begin ();
  void filter (const cmt_string& line);
  const cmt_string& result () const;

private:
  cmt_string m_result;
  bool m_first;
};

/**

     RecursivePass2 : after filtering the use statements really perform the
                      checkouts.

 */
class CvsImplementation;
class RecursivePass2 : public Awk
{
public:
  RecursivePass2 (CvsImplementation& cvs);
  void begin ();
  void filter (const cmt_string& line);

private:
  CvsImplementation& m_cvs;
};

/**

     RecursivePass3 : simply validate use statements in a project file
                      and echo those that really need to be handled.

 */
class RecursivePass3 : public Awk
{
public:

  void begin ();
  void filter (const cmt_string& line);
  const cmt_string& result () const;

private:
  cmt_string m_result;
  bool m_first;
};

/**

     RecursivePass4 : after filtering the use statements really perform the
                      project checkouts.

 */
class CvsImplementation;
class RecursivePass4 : public Awk
{
public:
  RecursivePass4 (CvsImplementation& cvs);
  void begin ();
  void filter (const cmt_string& line);

private:
  CvsImplementation& m_cvs;
};

/**

   Internal implementation of CVS to CMT operations.
   The Cvs class only provides abstract interface.

 */
class CvsImplementation
{
public:

  CvsImplementation ()
  {
    clear ();
  }

  void clear ()
  {
    m_recursive  = false;
    m_head       = false;
    m_verbose    = false;
    m_simulation = false;

    m_home_dir     = "";
    m_checkout_dir = "";
    m_version_dir  = "";
    m_cvs_offset   = "";

    m_last_module        = "";
    m_last_cvs_infos     = "";
    structure_info       = "";
    error_info           = "";
    tags_top_info        = "";
    tags_info            = "";
    cvsversions_top_info = "";
    cvsversions_info     = "";
    branches_info        = "";
    subpackages_info     = "";
    subprojects_info     = "";

    m_protocol_level     = "";

    Symbol* symbol = Symbol::find ("cmt_cvs_protocol_level");
    if (symbol != 0)
      {
	m_protocol_level = symbol->build_macro_value ();
	Symbol::expand (m_protocol_level);
      }
  }

  CvsImplementation& operator = (const CvsImplementation& other)
  {
    m_recursive  = other.m_recursive;
    m_head       = other.m_head;
    m_verbose    = other.m_verbose;
    m_simulation = other.m_simulation;

    m_home_dir       = other.m_home_dir;
    m_checkout_dir   = other.m_checkout_dir;
    m_version_dir    = other.m_version_dir;
    m_cvs_offset     = other.m_cvs_offset;
    m_protocol_level = other.m_protocol_level;
    m_last_module    = other.m_last_module;
    m_last_cvs_infos = other.m_last_cvs_infos;

    structure_info       = other.structure_info;
    error_info           = other.error_info;
    tags_top_info        = other.tags_top_info;
    tags_info            = other.tags_info;
    cvsversions_top_info = other.cvsversions_top_info;
    cvsversions_info     = other.cvsversions_info;
    branches_info        = other.branches_info;
    subpackages_info     = other.subpackages_info;
    subprojects_info     = other.subprojects_info;

    return (*this);
  }

  /**
     Filter out the space-separated words of a text that don't match a regexp.
   */
  void filter_list (cmt_string& text, const cmt_regexp& exp)
  {
    CmtSystem::cmt_string_vector list;

    CmtSystem::split (text, " ", list);

    int i;

    text = "";

    for (i = 0; i < list.size (); i++)
      {
	const cmt_string& s = list[i];
	if (exp.match (s))
	  {
	    if (i > 0) text += " ";
	    text += s;
	  }
      }
  }

  int execute (const cmt_string& command)
  {
    int status = 0;

    if (m_verbose || m_simulation)
      {
	cout << "#CMT> Executing [" << command << "]" << endl;
      }
    
    if (!m_simulation)
      {
	status = CmtSystem::execute (command);
      }

    return (status);
  }

  void execute_and_retry (const cmt_string& command, const cmt_string& message)
  {
    int status;
    int retry = 0;

    for (;;)
      {
	status = execute (command);
        
	if (status != 0)
	  {
	    retry++;
	    
	    cout << "# " << message << ": status=" << status << endl;
	    cout << "#---------------------------------------------------------" << endl;
	    
	    if (retry > 5) exit(0);
	  }
	else
	  {
	    break;
	  }
      }
  }

  int execute (const cmt_string& command, cmt_string& out)
  {
    int status = 0;

    if (m_verbose || m_simulation)
      {
	cout << "#CMT> Executing [" << command << "]" << endl;
      }
    
    if (!m_simulation)
      {
	status = CmtSystem::execute (command, out);
	
      }
      
    //if (m_verbose || m_simulation)
    //  {
    //       cout << out << endl;
    //  }
    return (status);
  }


  /**
     This function will check the protocol level with the CVS pluggin.
     The expected level is defined in a macro (in the requirements file of CMT)
     The protocol level characterizes the structure of the message received from the CVS pluggin
     and depends on its version.

     In order to know if the pluggin is installed AND if the support for this version is
     installed, we checkout CVSROOT/loginfo which should contain entries with selection patterns

     .cmtcvsinfos/<protocol_level>

     However old versions only offer as pattern:

     .cmtcvsinfos

     In this function we'll detect if the effective protocol level satisifies the 
     level expected for this release of CMT

     In simulation mode we suppose the expected protocol has been found.
   */
  bool check_protocol ()
  {
    static bool done = false;
    static bool found = true;

    if (done) return (found);
    done = true;

    cmt_string cvsroot;
	
    CmtSystem::get_cvsroot (cvsroot);
	
    cmt_string command;
	
    command = "cvs";
    if (cvsroot != "") 
      {
	command += " -d ";
	command += cvsroot;
      }
    command += " -Q co -p CVSROOT/loginfo ";

    found = false;

    cmt_string pattern = ".cmtcvsinfos";

    if (m_protocol_level != "")
      {
	pattern += "/";
    	pattern += m_protocol_level;
      }

    cmt_string loginfo;

    if (m_simulation)
      {
	loginfo = pattern;
      }

    execute (command, loginfo);

    int pos = loginfo.find (pattern);

    if (pos != cmt_string::npos)
      {
	found = true;
      }

    if (m_verbose)
      {
	if (found)
	  {
	    cout << "#CMT> Protocol level " << m_protocol_level << endl;
	  }
	else
	  {
	    cout << "#CMT> The CVS pluggin is not installed or is not at protocol level " << m_protocol_level << endl;
	  }
      }
    
    return (found);
  }

  /**
     Execute the CVS command that activates the CVS pluggin, ie this is a cvs import 
     using the conventional module .cmtcvsinfos/<protocol-level>/<module>

     We create a temporary directory just to lauch the command. However nothing
     should change in this temporary directory since the pluggin returns an error status.
   */
  void retreive_cvs_infos (const cmt_string& module)
  {
    static const cmt_string cmtcvsinfos = ".cmtcvsinfos";

    cmt_string home_dir = CmtSystem::pwd ();
	
    //
    // Activities related with .cmtcvsinfos will occur in a temporary directory
    //
    cmt_string tmp_dir = CmtSystem::getenv ("TMPDIR");
    if (tmp_dir == "")
      {
	tmp_dir = CmtSystem::file_separator ();
	tmp_dir += "tmp";
      }
    
    if (!CmtSystem::cd (tmp_dir))
      {
	tmp_dir = home_dir;
      }
    
    tmp_dir += CmtSystem::file_separator ();
    tmp_dir += "cmtcvs";
    {
      cmt_string temp = CmtSystem::get_temporary_name ();
      CmtSystem::basename (temp, temp);
      // Suppress dots for Windows
      temp.replace_all (".", "");
      tmp_dir += temp;
    }
    
    if (!CmtSystem::test_directory (tmp_dir))
      {
	if (!CmtSystem::mkdir (tmp_dir))
	  {
	    cout << "#CMT> Cannot create the temporary directory [" << tmp_dir << "]" << endl;
	    return;
	  }
      }
    
    //trap "rm -rf ${tmp_dir}" 0 1 2 15
    
    if (!CmtSystem::cd (tmp_dir))
      {
	cout << "#CMT> Cannot move to the temporary directory " << tmp_dir << endl;
	
	if (m_verbose)
	  {
	    cout << "#CMT> now removing tmp_dir " << tmp_dir << " home=" << home_dir << endl;
	  }
	
	CmtSystem::remove_directory (tmp_dir);
	
	return;
      }
    
    if (m_verbose)
      {
	cout << "#CMT> cvs infos are now obtained from the temporary directory " << CmtSystem::pwd () << endl;  
      }
    
    /**
       The script associated to such entries is supposed to :
        1) extract the set of <infos> from ${module}/cmt/requirements
                                        or ${module}/cmt/project.cmt
        2) build an output of the form :
            <infos>=info1 info2 info3 ...
      
       Currently this script can be found in
      
       ${CMTROOT}/cmt/cmt_buildcvsinfos2.sh
       %CMTROOT%/cmt/cmt_buildcvsinfos.py
       There is a C++ implementation as cmtcvs.exe
      
    */
    
    if (!CmtSystem::test_directory (cmtcvsinfos))
      {
	CmtSystem::mkdir (cmtcvsinfos);
      }
    
    CmtSystem::cd (cmtcvsinfos);
    
    cmt_string cvsroot;
    
    CmtSystem::get_cvsroot (cvsroot);
    
    cmt_string command;
    
    command = "cvs";
    if (cvsroot != "") 
      {
	command += " -d ";
	command += cvsroot;
      }
    command += " -Q import -m cmt ";
    
    command += cmtcvsinfos;
    
    if (m_protocol_level != "")
      {
	command += "/";
	command += m_protocol_level;
      }
    command += "/";
    command += module;
    command += " CMT v1";
    
    m_last_cvs_infos = "";

    execute (command, m_last_cvs_infos);
    
    if (m_verbose)
      {
	cout << "#CMT> now removing tmp_dir " << tmp_dir << " home=" << home_dir << endl;
      }
    
    CmtSystem::cd (home_dir);
    CmtSystem::remove_directory (tmp_dir);	
  }

  /**
     This method exploits the hook installed into the loginfo script.
     A communication is setup with a dummy CVS module named .cmtcvsinfos/<module>
    
     At import time, the contents of the file will be used to parameterize
     the script named cmt_buildcvsinfos2.sh (referenced in the loginfo script)
    
      This script performs a scan in the CVS repository for the following types of
     information :
    
       the recognized structure below this module (none, project, package)
       all top symbolic tags installed for the module
       all symbolic tags installed for the module
       all branches available below this module
       all subpackages installed below the module.
       all subprojects installed below the module.
    
        In principle, only modules corresponding to true CMT products (packages or projects) are considered.
          o tags are obtained from the requirements or the project file
          o branches are sub-directories which are not themselves packages
          o subpackages are sub-directories which are CMT packages
            (a subdirectory is always either a branch or a subpackage)
  */
  void get_cvs_infos_with_offset (const cmt_string& module)
  {
    if (!check_protocol ())
      {
	cout << "#CMT> The CVS pluggin is not installed or is not at protocol level " << m_protocol_level << endl;
	return;
      }

    if (module == "")
      {
	cout << "#CMT> cmt cvs needs a module name" << endl;
	return;
      }

    if (module == m_last_module)
      {
	if (m_verbose)
	  {
	    cout << "#CMT> cvs infos for module " << module << " already there" << endl;
	  }
      }
    else
      {
	m_last_module = module;
        
	retreive_cvs_infos (module);
      }
    
    /**
       Now retrieve all info fields :
    */

    Grep grep;

    grep.run (m_last_cvs_infos, "structure=");
    
    if (grep.result () != "")
      {
	structure_info = grep.result ();
	structure_info.replace ("structure=", "");
      }
    else
      {
	// This may happen for old protocol level < v1r1
	structure_info = "package";
      }
    
    grep.run (m_last_cvs_infos, "error=");
    
    if (grep.result () != "")
      {
	error_info = grep.result ();
	error_info.replace ("error=", "");
      }
    else
      {
	error_info = "";
      }
    
    grep.run (m_last_cvs_infos, "tags_top=");
    
    if (grep.result () != "")
      {
	tags_top_info = grep.result ();
	tags_top_info.replace ("tags_top=", "");
      }
    else
      {
	tags_top_info = "";
      }
    
    grep.run (m_last_cvs_infos, "tags=");
    
    if (grep.result () != "")
      {
	tags_info = grep.result ();
	tags_info.replace ("tags=", "");
      }
    else
      {
	tags_info = "";
      }
    
    grep.run (m_last_cvs_infos, "cvsversions_top=");
    
    if (grep.result () != "")
      {
	cvsversions_top_info = grep.result ();
	cvsversions_top_info.replace ("cvsversions_top=", "");
      }
    else
      {
	cvsversions_top_info = "";
      }
    
    grep.run (m_last_cvs_infos, "cvsversions=");
    
    if (grep.result () != "")
      {
	cvsversions_info = grep.result ();
	cvsversions_info.replace ("cvsversions=", "");
      }
    else
      {
	cvsversions_info = "";
      }
    
    grep.run (m_last_cvs_infos, "branches=");
    
    if (grep.result () != "")
      {
	branches_info = grep.result ();
	branches_info.replace ("branches=", "");
      }
    else
      {
	branches_info = "";
      }
    
    grep.run (m_last_cvs_infos, "subpackages=");
    
    if (grep.result () != "")
      {
	subpackages_info = grep.result ();
	subpackages_info.replace ("subpackages=", "");
      }
    else
      {
	subpackages_info = "";
      }
    
    grep.run (m_last_cvs_infos, "subprojects=");

    if (grep.result () != "")
      {
	subprojects_info = grep.result ();
	subprojects_info.replace ("subprojects=", "");
      }
    else
      {
	subprojects_info = "";
      }

    /**
       The CMTCVSTAGFILTER env. var. may contain a regexp that will exclude some tags
       from the answr of the CVS pluggin.
       The pattern is a regexp but it may also contain the <package> template
     */
    cmt_string tag_filter = CmtSystem::getenv ("CMTCVSTAGFILTER");
    
    if (tag_filter != "")
      {
	cmt_string package;
	CmtSystem::basename (module, package);
	
	cmt_string pattern = "<package>";
	
	tag_filter.replace_all (pattern, package);
	
	cmt_regexp exp (tag_filter);
	
	cmt_string text;
	
	filter_list (tags_top_info, exp);
	filter_list (tags_info, exp);
	filter_list (cvsversions_top_info, exp);
	filter_list (cvsversions_info, exp);
      }
  }
  
  void get_cvs_infos (const cmt_string& cvs_offset, const cmt_string& module)
  {
    cmt_string full_name;

    if (cvs_offset != "") 
      {
	full_name = cvs_offset;
	full_name += "/";
	while (full_name.find ("//") != cmt_string::npos)
	  {
	    full_name.replace_all ("//", "/");
	  }
      }

    full_name += module;

    get_cvs_infos_with_offset (full_name);
  }

  /**
     From a space-separated list of version tags, try to find one tag
     matching a given regular expression.

       o The first matching tag is returned into 'version'
       o Success is returned as function value.

   */
  bool match_version_request (const cmt_string& text, 
                              const cmt_regexp& version_exp,
                              cmt_string& version)
  {
    CmtSystem::cmt_string_vector vs;
      
    CmtSystem::split (text, " \t", vs);

    version = "";

    for (int i = 0; i < vs.size (); i++)
      {
	const cmt_string& vv = "^"+ vs[i] + "$";
        
	if (version_exp.match (vv))
	  {
	    version = vv;
	    return (true);
	  }
      }

    return (false);
  }

  void get_module (const cmt_string& offset,
		   const cmt_string& product,
		   cmt_string& module)
  {
    module = "";

    if (offset != "")
      {
	module = offset;
	module += "/"; // This is for CVS only thus we don't use the real separator.
	while (module.find ("//") != cmt_string::npos)
	  {
	    module.replace_all ("//", "/");
	  }
      }

    module += product;
  }

  bool get_version (const cmt_string& offset,
                    const cmt_string& product,
                    const cmt_string& version_request,
                    const cmt_string& module,
                    cmt_string& version,
                    bool& at_head)
  {
    Grep grep;
    cmt_string topversions;
    cmt_string versions;
    cmt_string requested_version = version_request;
    
    at_head = false;
            
    /**
     *   Try to figure out what is the effective version tag available
     *   for the requested version expressions (which may contain
     *   wild card)
     *
     *     the requested version may either be in the top tags (ie those
     *     corresponding to the same most recent CVS version number before
     *     the HEAD) or not. The returned at_head flag will show this.
     *
     *     then the requested product may either be located in the CVS repository
     *     under the offset or not, the returned module will contain the effective
     *     location where the requested version has been found.
     */
    
    if (m_verbose)
      {
	     cout << "#CMT> requesting cvs infos onto module " << module << endl;
      }
    
    get_cvs_infos_with_offset (module);
    
    if (error_info != "")
      {
	versions = "";
	cout << "#CMT> Product " << product << " not found in ${CVSROOT}" << endl;
	return (false);
      }

    versions = tags_top_info;

    cmt_string v = version_request;
    
    if (version_request.find ("*") != cmt_string::npos)
      {
	v.replace_all ("*", ".*");
      }
    else
      {
	// this is an exact match to the end of the word since there is no wild card
	v += "$";
      }
    
    cmt_regexp version_exp (v);
    
    if (!match_version_request (versions, version_exp, version))
      {
	// We try on non-top versions

	versions = tags_info;

	if (!match_version_request (versions, version_exp, version))
	  {
	    version = requested_version;
	    int pos = 0;
	    if ((pos = version.find ("*")) != cmt_string::npos)
	      {
		//
		//  There was a wild card but the expression does not match
		// any of the existing tags in CVS.
		//  Things will be retreived from HEAD but we have to build
		// a reasonable version tag from the wild card expression.
		//  If the letter before the * was a digit, then simply remove
		// the * (v5* -> v5) otherwise add a zero (v5r* -> v5r0)
		//
		if (pos > 0)
		  {
		    char letter = version[pos-1];
		    
		    static const cmt_string digits = "0123456789";
		    
		    if (digits.find (letter) == cmt_string::npos)
		      {
			// "v5r*" -> "v5r0"
			version.replace ("*", "0");
		      }
		    else
		      {
			// "v5*" -> "v5"
			version.replace ("*", "");
		      }
		  }
		else
		  {
		    // The expression was simply "*" !!!
		    version = "v0";
		  }
	      }
	    at_head = true;
	  }
	else
	  {
	    at_head = false;
	  }
      }
    else
      {
	at_head = true;
      }
    
    /**
     *   Here we have at least one version matching the requested expression.
     */
    
    return (true);
  }

  bool do_need_version ()
  {
     bool need_version = false;

    if (structure_info == "project")
    {
  	     need_version = true;
  	     //CmtStructuringStyle style = Cmt::get_current_structuring_style ();
         //if (style == default_structuring_style)
	     //{
	     //   Use& current_use = Use::current ();
	     //   if (current_use.get_strategy ("VersionDirectory"))
	     //      need_version = true;
	     //}
	     //else if (style == with_version_directory)
	     //    need_version = true;	  	       
  	     // cout<<"need version"<<need_version<<endl;
  	}
    else
      {
	     CmtStructuringStyle style = Cmt::get_current_structuring_style ();
         if (style == default_structuring_style)
	     {
	        Use& current_use = Use::current ();
	        if (current_use.get_strategy ("VersionDirectory"))
	           need_version = true;
	     }
	     else if (style == with_version_directory)
	         need_version = true;	        
      }

    return (need_version);
  }

  /**
     Take care of structuring style for packages and of project vs package conventions
   */
  cmt_string build_version_directory (const cmt_string& offset,
                                      const cmt_string& product,
                                      const cmt_string& version)
  {
    cmt_string dir = m_home_dir;
    
    if (m_checkout_dir != "")
      {
	// consider the usual -d option 
 
	dir += CmtSystem::file_separator ();
	dir += m_checkout_dir;
      }
    
    dir += CmtSystem::file_separator ();
    dir += offset;
    dir += CmtSystem::file_separator ();
    dir += product;

    if (do_need_version ())
      {
	dir += CmtSystem::file_separator ();
	dir += version;
      }
    
    CmtSystem::reduce_file_separators (dir);
    
    return (dir);
  }

  /**
     Wrapper to mkdir handling simulation and verbose options.
   */
  bool mkdir (const cmt_string& dir)
  {
    if (m_simulation)
      {
	cout << "#CMT> Would create the " << dir << " directory" << endl;
      }
    else
      {
	if (!CmtSystem::cd (dir))
	  {
	    if (m_verbose)
	      {
		cout << "#CMT> About to mkdir " << dir << endl;
	      }

	    CmtSystem::mkdir (dir);
	    if (!CmtSystem::cd (dir))
	      {
		cout << "# Error creating the directory :" << dir << endl;
		cout << "#---------------------------------------------------------" << endl;
		return (false);
	      }
	  }
      }
    return (true);
  }

  /**
     When running cmt cvs commands, we stand by definition outside of any existing 
     package context. Thus it's likely that CMTPATH are not completely defined.
     This function manually prepends CMTPATH entries to the environment variable.
   */
  void add_cmtpath (const cmt_string& dir)
  {
    static cmt_string CMTPATH;

    cmt_string cmtpath = Symbol::get_env_value ("CMTPATH");

    if (cmtpath.find (dir) == cmt_string::npos)
      {
	CMTPATH = dir;
	CMTPATH += ":";
	CMTPATH += cmtpath;

	CmtSystem::putenv ("CMTPATH", CMTPATH);
      }

    if (m_verbose)
      {
	cout << "#CMT> CMTPATH=" << CmtSystem::getenv ("CMTPATH") << endl;
      }
  }

  /**
     Specific checkout of one project
   */
  bool really_checkout_project_contents (const cmt_string& offset,
					 const cmt_string& project,
					 const cmt_string& version,
					 const cmt_string& tag,
					 const cmt_string& module,
					 const cmt_string& basedir,
					 bool at_head,
					 const cmt_string& currentdir)
  {
    cmt_string dir = currentdir;

    cout << "  # get project files into " << dir << endl;


    cmt_string version_dir = version;
    if (!mkdir (version_dir)) return (false);

    dir += CmtSystem::file_separator ();
    dir += version_dir;
    
    cmt_string command = "cvs -f -Q co -P ";
    if (!at_head)
      {
	command += "-r ";
	command += (tag != "") ? tag : version;
      }

    command += " -d cmt ";

    command += " ";
    command += module;
    command += "/cmt";

    command += CmtSystem::command_separator ();

    command += " cvs -f update -l .";

    execute_and_retry (command, "Error getting project CMT contents");

    return (true);
  }
  /**
     Construct CVS management files in the top directory. This is needed
     if the top directory of a product is empty. (In this case the
     co -l results in nothing)
   */
  void make_management_files (const cmt_string& module,
			      const cmt_string& entries_text)
  {
    if (!CmtSystem::test_directory ("CVS"))
      {
	/**
	 * The CVS repository had not been created (this is generally
	 * due to the lack of top files)
	 */

	if (!mkdir ("CVS")) return;

	CmtSystem::cd ("..");

	cmt_string s;
	
	// Let's create first the CVS/Root file.
	
	CmtSystem::get_cvsroot (s);
	s += "\n";
	
	cmt_string f;
	
	f = "CVS";
	f += CmtSystem::file_separator ();
	f += "Root";
	
	if (m_simulation)
	  {
	    cout << "#CMT> Would fill in the CVS/Root file with " << endl;
	    cout << s << endl;
	  }
	else
	  {
	    if (m_verbose)
	      {
		cout << "#CMT> Fill in the CVS/Root file with " << endl;
		cout << s << endl;
	      }
	    s.write (f);
	  }
	
	// Now we create the CVS/Repository file
	
	f = "CVS";
	f += CmtSystem::file_separator ();
	f += "Repository";
	
	CmtSystem::get_cvsroot (s);
	if (s[0] == ':')
	  {
	    int pos = s.find (1, ":");
	    s.erase (0, pos+1);
	    pos = s.find (0, ":");
	    s.erase (0, pos+1);
	  }
	s += "/";
	s += module;
	s += "\n";
	
	if (m_simulation)
	  {
	    cout << "#CMT> Would fill in the CVS/Repository file with " << endl;
	    cout << s << endl;
	  }
	else
	  {
	    if (m_verbose)
	      {
		cout << "#CMT> Fill in the CVS/Repository file with " << endl;
		cout << s << endl;
	      }
	    s.write (f);
	  }
      }
    
    if (m_simulation)
      {
	cout << "#CMT> Would write the top CVS/Entries file with " << endl;
	cout << entries_text << endl;
      }
    else
      {
	cmt_string entries_file_name;

	entries_file_name = "CVS";
	entries_file_name += CmtSystem::file_separator ();
	entries_file_name += "Entries";
    
	cmt_string text;

	if (!text.read (entries_file_name))
	  {
	    // This happens when there were no top files
	  }

	text += entries_text;

	// Now the CVS/Entries is ready to be created.
	if (m_verbose)
	  {
	    cout << "#CMT> Fill in the top CVS/Entries file with " << endl;
	    cout << text << endl;
	  }

	text.write (entries_file_name);
      }
  }


  /**
     Specific checkout of one package

     1) get top files (no directories)
     2) construct the directory structure (with or without version directory)
     3) build the CVS/Entries file for subdirs and co individual subdirs
     4) write the CVS management files if CVS did not do it.
   */
  bool really_checkout_package_contents (const cmt_string& offset,
					 const cmt_string& package,
					 const cmt_string& version,
					 const cmt_string& module,
					 const cmt_string& basedir,
					 bool at_head,
					 const cmt_string& currentdir)
  {
    cmt_string dir = currentdir;

    cout << "  # get top files " << endl;
            
    cmt_string command = "cvs -f -Q co -l ";
    if (!at_head)
      {
	command += "-r ";
	command += version;
      }

    bool need_version = do_need_version ();

    if (need_version)
      {
	command += " -d ";
	command += version;
      }
    else
      {
	command += " -d ";
	command += package;

	// Must stand just above the package directory
	CmtSystem::cd ("..");
	CmtSystem::dirname (dir, dir);
      }
    
    command += " ";
    command += module;
    
    execute_and_retry (command, "Error getting package CMT contents");

    if (need_version)
      {
	       if (!mkdir (version)) return (false);	
	       dir += CmtSystem::file_separator ();
	       dir += version;
      }
    else
      {
	if (!mkdir (package)) return (false);
	
	dir += CmtSystem::file_separator ();
	dir += package;
      }
    
    if (m_verbose)
      {
	cout << "#CMT> Now getting subdirectories pwd=" << CmtSystem::pwd () << " dir=" << dir << endl;
      }
    
    cmt_string branches = CmtSystem::getenv ("CMTCVSBRANCHES");
    
    if (branches == "")
      {
	    branches = branches_info;
      }
    
    CmtSystem::cmt_string_vector branch_vector;
    
    CmtSystem::split (branches, " \t", branch_vector);
      
    cmt_string text = "";
    
    command = "";

    if (!CmtSystem::test_directory ("CVS"))
      {
          int i;
          for (i = 0; i < branch_vector.size (); i++)
            {
	       cmt_string& branch = branch_vector[i];	
	       if (i > 0)
	           {
	              command += CmtSystem::command_separator ();
	           }
	
	       command += "cvs -f -Q co ";
	
	       if (!at_head)
	          {
	             command += "-r ";
	             command += version;
	          }
	
	       command += " -d ";
	       command += branch;
	       command += " ";
	       command += module;
	       command += "/";    // CVS uses the '/' notation on all platforms!!
	       command += branch;
	
	       text += "D/";
	       text += branch;
	       text += "////\n";
            } 
        execute_and_retry (command, "Error getting package contents");

        make_management_files (module, text);

        if (need_touch_files)
        {  
    
            cout << "# --> update the file timestamps" << endl;                 
            for (i = 0; i < branch_vector.size (); i++)
            {	
	            cmt_string& branch = branch_vector[i];    
                CmtSystem::cmt_string_vector& list = CmtSystem::scan_dir (branch);
                int j;
                for (j = 0; j < list.size (); j++)
                    if (CmtSystem::test_file(list[j]))                
                        CmtSystem::touch_file (list[j]);
            }
        }        
        return (true);   
      }
 
    command += "cvs -Q update -d ";

    if (!at_head)
      {
        command += "-r ";
        command += version;
      }

    int i;
    
    for (i = 0; i < branch_vector.size (); i++)
      {
	    cmt_string& branch = branch_vector[i];
            command += " ";
	    command += branch;
      }

    command += CmtSystem::command_separator ();

    command += "cvs update -l .";
    
    execute_and_retry (command, "Error getting package contents");

    if (need_touch_files)
    {  
    
        cout << "# --> update the file timestamps" << endl;                 
        for (i = 0; i < branch_vector.size (); i++)
        {	
	        cmt_string& branch = branch_vector[i];    
            CmtSystem::cmt_string_vector& list = CmtSystem::scan_dir (branch);
            int j;
            for (j = 0; j < list.size (); j++)
                if (CmtSystem::test_file(list[j]))                
                    CmtSystem::touch_file (list[j]);
        }
    }        
    return (true);
  }

  /**
     Effective checkout of a package or a project
   */
  bool really_checkout (const cmt_string& offset,
			const cmt_string& product,
			const cmt_string& version,
			const cmt_string& tag,
			const cmt_string& module,
			const cmt_string& basedir,
			bool at_head)
  {
    cmt_string dir = basedir;
    cmt_string out;
    
    cout << "# ================= working on " << structure_info << " " << product 
	 << " version " << version;

    if (at_head) cout << " (At head) ";

    cmt_string full_offset;

    full_offset = m_cvs_offset;
    full_offset += offset;

    cmt_string echo_ppath;
        
    if (offset != "")
      {
	echo_ppath = " path ";
	echo_ppath += offset;
      }
    
    cout << echo_ppath << " in " << dir << endl;

    if (do_need_version ())
      {
	// Move back to the product name.
	CmtSystem::dirname (dir, dir);
      }

    if (!mkdir (dir)) return (false);
	
    if (structure_info == "package")
      {
	really_checkout_package_contents (offset,
					  product,
					  version,
					  module,
					  basedir,
					  at_head,
					  dir);
      }
    else if (structure_info == "project")
      {
	really_checkout_project_contents (offset,
					  product,
					  version,
					  tag,
					  module,
					  basedir,
					  at_head,
					  dir);
      }

    return (true);
  }

  /**
     Find the most appropriate effective version directory corresponding to the 
     specified version expression.
     The specified expression may contain wildcards (in the file manager sense). This
     FME is first converted into a RE then a directory search is performed.
     An empty string is returned if no match is found.
   */
  cmt_string find_matching_version (const cmt_string& expression)
  {
      
    cmt_string result;     
    
    //
    // Here expression takes the form
    //   <some path>/<expression with wild-card>
    //

    cmt_string dir;
    CmtSystem::dirname (expression, dir);
    dir += CmtSystem::file_separator ();
    
    cmt_string version;
    CmtSystem::basename (expression, version);

    if (version.find ("*") == cmt_string::npos)
      {
	// there is no wildcarding here. A simple test is enough.
	if (CmtSystem::test_directory (expression))
	  {
	    result = version;
	  }
      }
    else
      {
	version.replace ("*", ".*");
	
	cmt_regexp exp (version);
	
	CmtSystem::cmt_string_vector list;
	
	CmtSystem::scan_dir (dir, exp, list);
	
	if (list.size () > 0)
	  {
	    result = list[0];
	    
	    CmtSystem::basename (result, result);
	  }
      }
    
    return (result);
  }
  
  /**
   *   We provide a path to a requirements file. From it we read the use 
   *  statements, and we try to checkout the corresponding packages.
   *
   *   A boolean return tells if any recursion occurred.
   */
  bool checkout_from_requirements (const cmt_string& requirements_path)
  {
    static cmt_regexp expression ("^[ \t]*use[ \t]");

    cmt_string text;

    text.read (requirements_path);

    RecursivePass1 p1;
    p1.run (text, expression);

    bool result = (p1.result () != "");
    RecursivePass2 p2 (*this);
    p2.run (p1.result ());

    return (result);
  }

  /**
   *   We provide a path to a project file. From it we read the use 
   *  statements, and we try to checkout the corresponding projects.
   */
  void checkout_from_project_file (const cmt_string& file_name)
  {
    static cmt_regexp expression ("^[ \t]*use[ \t]");

    cmt_string text;

    text.read (file_name);

    CvsImplementation& me = *this;
    CvsImplementation saved;
    saved = me;
    cmt_string here = CmtSystem::pwd ();

    RecursivePass3 p3;
    p3.run (text, expression);

    RecursivePass4 p4 (*this);
    p4.run (p3.result ());

    Grep grep;

    grep.run (text, "container");
    cmt_string container = grep.result ();

    if (container != "")
      {
	static cmt_regexp container_expression ("^[ \t]*container[ \t]");

	add_cmtpath (here);

	cout << "  # --> now getting project packages from the " << container << " " << here << endl;

	CmtSystem::cd (here);

	RecursivePass1 p1;
	p1.run (text, container_expression);

	RecursivePass2 p2 (*this);

	m_home_dir = CmtSystem::pwd ();
	p2.run (p1.result ());
      }

    CmtSystem::cd (here);
    me = saved;
  }

  /**
     Check whether a given directory structure matches an expected product structure
     given by the structure info obtained from the most recent request to the CVS pluggin
   */
  bool check_structure (const cmt_string& dir, const cmt_string& version)
  {
    bool result = false;
    if (!CmtSystem::test_directory (dir))
      {
	       return (false);
      }

    if (structure_info == "package")
      {
	// Check if it is a true CMT package.	
	cmt_string file_name;
	
	file_name = dir;
	file_name += CmtSystem::file_separator ();
	file_name += "cmt";
	file_name += CmtSystem::file_separator ();
	file_name += "requirements";
	
	if (CmtSystem::test_file (file_name))
	  {	  
	       if (!do_need_version ())
	       {
	           // open the version.cmt file and compare the version
                 cmt_string current_version;
                 cmt_string version_file = dir;
                 version_file += CmtSystem::file_separator ();
                 version_file += "CVS";
                 version_file += CmtSystem::file_separator ();
                 version_file += "Tag"; 
                 current_version.read (version_file);               
                 //cout <<version_file<<", "<<current_version<<", "<<"N"+version+"\n"<<endl;
                 return current_version=="N"+version+"\n";	            
	        }   
	       	else
		       result = true;
	  }
	else
	  {

	    file_name = dir;
	    file_name += CmtSystem::file_separator ();
	    file_name += "mgr";
	    file_name += CmtSystem::file_separator ();
	    file_name += "requirements";
	    
	    if (CmtSystem::test_file (file_name))
	      {
	       if (!do_need_version ())
	       {
	           // open the version.cmt file and compare the version
                 cmt_string current_version;
                 cmt_string version_file = dir;
                 version_file += CmtSystem::file_separator ();
                 version_file += "CVS";
                 version_file += CmtSystem::file_separator ();
                 version_file += "Tag"; 
                 current_version.read (version_file);               
                 //cout <<version_file<<", "<<current_version<<", "<<"N"+version+"\n"<<endl;
                 return current_version=="N"+version+"\n";	            
	        }   
	       	else
		       result = true;
	      }
	  }
      }
    else if (structure_info == "project")
      {
	cmt_string file_name;
	
	file_name = dir;
	file_name += CmtSystem::file_separator ();
	file_name += "cmt";
	file_name += CmtSystem::file_separator ();
	file_name += "project.cmt";
	
	if (CmtSystem::test_file (file_name))
	  {
	    result = true;
	  }
    }

    return (result);
  }

  /**
     Internal call from the initial do_checkout or from recursive passes
     Prepare the directory structure for the checkout
     Do the checkout
     Check if everything was done properly, if a package or a project has been created
     If needed recurse from the requirements or project file

     For projects there may be two different specifications for
     - the version directory
     - the CVS tag
   */
  void do_checkout_phase2 (const cmt_string& offset,
			   const cmt_string& product,
			   const cmt_string& specified_version,
			   const cmt_string& tag)
  {
    if (m_verbose)
      {
	cout << "#CMT> do_checkout_phase2> offset=" << offset
	     << " " << structure_info << "=" << product
	     << " specified_version=" << specified_version
	     << " tag=" << tag
	     << " pwd=" << CmtSystem::pwd ()
	     << endl;
      }
    
    cmt_string version = specified_version;
    cmt_string empty;
    cmt_string full_offset;

    full_offset = m_cvs_offset;
    full_offset += offset;
    
    cmt_string echo_ppath;
    
    if (offset != "")
      {
	echo_ppath = " path ";
	echo_ppath += offset;
      }
    
    if (version == "")
      {
	cout << "# ================= No version specified for " << structure_info << " " << product << endl;
	return;
      }
    
    //
    //  First make an attempt to locate the specified version of
    //  this product "as-it-is" in the work area.
    //   Since 'version' may contain wild-card, it's likely that
    //  we should not simply use CmtSystem::test_directory but
    //  use the wild-card search.
    //
    
    cmt_string dir;
    
    dir = build_version_directory (offset, product, version);
    
    bool recursive = m_recursive;
    
    cmt_string effective_version = "";
    if (do_need_version ())
    {
    /* Important this part should be enhanced ASAP 
       and deal with many other cases.... arghhhh !!
    */    
        effective_version = find_matching_version (dir);         
    }


         
    cmt_string module;
    get_module (full_offset, product, module);

    cmt_string cvs_tag = (tag != "") ? tag : version;
    bool at_head = false;

    if (effective_version != "")
      {
	version = effective_version;
      }
    else
      {
	//
	// get_version attempts to find the most appropriate version
	// tag matching the specification FROM the repository. However,
	// we should take into account situations where some versions have
	// already been checked out, in which case they might be sufficient
	// (or preferred?)
	//
	
	if (cvs_tag.find ("*") != cmt_string::npos)
	  {
	    cout << "# ================= " << structure_info << " " << product 
		 << " version " << cvs_tag << echo_ppath 
		 << " has wild cards and will not be considered." << endl;
	    return;
	  }
	
	if (!get_version (full_offset, product, cvs_tag, module,
			  cvs_tag, at_head))
	  {
	    return;
	  }
	
	if (m_head)
	  {
	    m_head = false;
	    
	    at_head = true;
	  }
	else
	  {
	    at_head = false;
	  }
	
	//
	// Make a second try after having selected a CVS tag from all the
	// available tags compatible with the specified version
	//

	if (tag == "")
	  {
	    // If tag was not specified, then the version directory has to match the CVS tag
	    // Otherwise the original version specification is kept for the directory.
	    version = cvs_tag;
	    dir = build_version_directory (offset, product, version);
	  }
      }
    
    if (check_structure (dir, version))
      {
	     cout << "# ================= " << structure_info << " " << product 
	          << " version "            << version        << echo_ppath 
	          << " already installed."  << endl;	
	     recursive = false;
      }
    else
      {
	//
	// Now we can say that we have to perform the real checkout.
	// 
	
	if (!really_checkout (offset, product, version, cvs_tag, module, dir, at_head))
	  {
	    cout << "# bad return from really_checkout_product" << endl;
	    return;
	  }
      }

    //
    //  Now reach the newly checked out product.
    //
    
    if (m_simulation)
      {
	cout << "#CMT> " << structure_info << " directory not really created " << dir << endl;
      }
    else if (structure_info == "package")
      {
	if (!CmtSystem::cd (dir))
	  {
	    cout << "#CMT> Package directory not created " << dir << endl;
	    return;
	  }
	
	// Check if it is a true CMT package.
	
	cmt_string file_name;
	
	file_name = "cmt";
	file_name += CmtSystem::file_separator ();
	file_name += "requirements";
	
	if (CmtSystem::test_file (file_name))
	  {
	    dir += CmtSystem::file_separator ();
	    dir += "cmt";
	    CmtSystem::cd ("cmt");
	    
	    if (!do_need_version ())
	      {
		cmt_string text = version;
		text += "\n";
		text.write ("version.cmt");
	      }
	  }
	else
	  {
	    file_name = "mgr";
	    file_name += CmtSystem::file_separator ();
	    file_name += "requirements";
	    
	    if (CmtSystem::test_file (file_name))
	      {
		dir += CmtSystem::file_separator ();
		dir += "mgr";
		CmtSystem::cd ("mgr");
	      }
	    else
	      {
		cout << "# " << product << " not a CMT package" << endl;
		return;
	      }
	  }
	
	if (recursive)
	  {
	    cmt_string here = CmtSystem::pwd ();

	    bool did_recurse = checkout_from_requirements ("requirements");

	    CmtSystem::cd (here);

	    if (did_recurse) 
	      {
		execute ("cmt -quiet broadcast cmt -quiet config");
	      }
	    else
	      {
		execute ("cmt -quiet config");
	      }
	  }
	else
	  {
	    execute ("cmt -quiet config");
	  }
      }
    else if (structure_info == "project")
      {
	
	if (m_verbose)
	  {
	    cout << "#CMT> dir=" << dir << endl;
	  }
	
	if (!CmtSystem::cd (dir))
	  {
	    cout << "#CMT> Project directory not created " << dir << endl;
	    return;
	  }
	
	cmt_string file_name;
	
	file_name = "cmt";
	file_name += CmtSystem::file_separator ();
	file_name += "project.cmt";
	
	if (!CmtSystem::test_file (file_name))
	  {
	    cout << "# " << product << " not a CMT project" << endl;
	    return;
	  }
	
	if (recursive)
	  {
	    checkout_from_project_file (file_name);
	  }

	cout << "# ================= Project " << product << " completed" << endl;

      }
  }
  
  /**
     Top level of the checkout operation, initiated from the command line arguments

     Construct an history of the checkouts to avoid duplicating the
     checkouts during the recursivity

     Eventually 
     o perform the cmt config for packages.

   */
  void do_checkout_phase1 (const cmt_string& module, 
			   const cmt_string& version_dir, 
			   const cmt_string& version_tag)
  {
    add_cmtpath (m_home_dir);

    History& h = History::instance ();

    h.clear ();

    if (module == "")
      {
	if (m_verbose)
	  {
	    cout << "#CMT> Missing module name" << endl;
	  }
	return;
      }

    cmt_string offset;
    cmt_string product;
    cmt_string version;
    cmt_string tag;
    
    {
      cmt_string m;
      m = m_cvs_offset;
      m += module;
        
      get_cvs_infos_with_offset (m);
        
      if (error_info != "")
	{
	  cout << error_info << endl;
	  return;
	}
    }

    if (version_tag == "")
      {
        Cut cut (0);
        
        if (tags_top_info != "") tag = tags_top_info;
        else tag = tags_info;
        
        cut.run (tag);
        
        tag = cut.result ();
      }
    else
      {
	tag = version_tag;
      }

    version = (version_dir == "") ? tag : version_dir;

    CmtSystem::dirname (module, offset);
    CmtSystem::basename (module, product);
    
    cmt_string top_dir;
        
    top_dir = m_home_dir;
    top_dir += CmtSystem::file_separator ();
    top_dir += m_checkout_dir;
    top_dir += CmtSystem::file_separator ();
    top_dir += offset;
    top_dir += CmtSystem::file_separator ();
    top_dir += product;
    top_dir += CmtSystem::file_separator ();
    top_dir += version;

    CmtSystem::reduce_file_separators (top_dir);

    if (m_verbose)
      {
	cout << "#CMT> about to checkout " << structure_info
	     << " " << product << " version " << version << " into " << top_dir << endl;
      }

    static const cmt_string empty;
    do_checkout_phase2 (offset, product, version, tag);

    if (m_simulation) return;

    if (!CmtSystem::cd (top_dir)) return;

    if (structure_info == "project")
      {
	cmt_string file_name;
    
	file_name = "cmt";
	file_name += CmtSystem::file_separator ();
	file_name += "project.cmt";
	
	if (!CmtSystem::test_file (file_name))
	  {
	    cout << "# " << product << " was not properly checked out and is missing its cmt/project.cmt file" << endl;
	    return;
	  }
      }
    else
      {
	cmt_string file_name;
    
	file_name = "cmt";
	file_name += CmtSystem::file_separator ();
	file_name += "requirements";
	
	if (CmtSystem::test_file (file_name))
	  {
	    top_dir += CmtSystem::file_separator ();
	    top_dir += "cmt";
	    CmtSystem::cd ("cmt");
	  }
	else
	  {
	    file_name = "mgr";
	    file_name += CmtSystem::file_separator ();
	    file_name += "requirements";
	    
	    if (CmtSystem::test_file (file_name))
	      {
		top_dir += CmtSystem::file_separator ();
		top_dir += "mgr";
		CmtSystem::cd ("mgr");
	      }
	    else
	      {
		cout << "# " << product << " was not properly checked out and is missing its cmt/requirements file" << endl;
		return;
	      }
	  }
	
	if (m_verbose)
	  {
	    cout << "#CMT> product " << product << " has been checked out" << endl;
	  }
	
	if (!m_recursive)
	  {
	    execute ("cmt -quiet config");
	  }
      }
  }

  void help ()
  {
    cout << "> cd <some work area>" << endl;
    cout << "> cmt checkout [modifier ...] <package|project>" << endl;
    cout << "" << endl;
    cout << "   modifier :" << endl;
    cout << "   -l        Do not process used packages (default)." << endl;
    cout << "   -R        Process used products recursively." << endl;
    cout << "   -r rev    Check out version tag. (is sticky)" << endl;
    cout << "   -vd dir   Use this version directory instead of CVS tag." << endl;
    cout << "   -d dir    Check out into dir instead of module name." << endl;
    cout << "   -o offset Offset in the CVS repository" << endl;
    cout << "   -requirements <requirements file path>  Check out packages referenced in this requirements file" << endl;
    cout << "   -t  Change file timestamps to the date of checkout" << endl;
    cout << "   -n        simulation mode on" << endl;
    cout << "   -v        verbose mode on" << endl;
    cout << "   --help    print this help" << endl;
    cout << "" << endl;
    cout << "> cmt cvstags <package|project>" << endl;
    cout << "> cmt cvssubpackages <directory>" << endl;
    cout << "> cmt cvssubprojects <directory>" << endl;
    cout << "" << endl;
  }

  /**
     Implementation of the cmt cvstags
     Get the CVS tags of a module
   */
  void tags (const CmtSystem::cmt_string_vector& arguments)
  {
    if (arguments.size () < 1)
      {
	help ();
	return;
      }
    
    if (CmtSystem::getenv ("CVSROOT") == "")
      {
	cout << "# Please set CVSROOT first !" << endl;
	return;
      }
    
    m_cvs_offset = CmtSystem::getenv ("CMTCVSOFFSET");
    if (m_cvs_offset != "") 
      {
	m_cvs_offset += "/";
	m_cvs_offset.replace_all ("//", "/");
      }
    
    bool all = false;
    
    for (int arg = 0; arg < arguments.size (); arg++)
      {
	const cmt_string& option = arguments[arg];
	
	if (option == "-all")
	  {
	    all = true;
	  }
	else
	  {
	    get_cvs_infos (CmtSystem::getenv ("CMTCVSOFFSET"), option);
	    
	    if (error_info != "")
	      {
		cout << error_info << endl;
	      }
	    else
	      {
		cmt_string tags;
		
		if (all)
		  {
		    tags = cvsversions_top_info;
		    tags += " ";
		    tags += cvsversions_info;
		  }
		else
		  {
		    tags = tags_top_info;
		    tags += " ";
		    tags += tags_info;
		  }
		
		CmtSystem::cmt_string_vector v;
		
		CmtSystem::split (tags, " \t", v);
		for (int i = 0; i < v.size (); i++)
		  {
		    const cmt_string& s = v[i];
		    cout << s << endl;
		  }
	      }
	  }
      }
  }

  /**
     Implementation of the cmt cvsbranches
     Get the subdirs of a module that are not subackages
   */
  void branches (const cmt_string& module)
  {
    cmt_string out;
    
    get_cvs_infos (CmtSystem::getenv ("CMTCVSOFFSET"), module);
    
    if (error_info != "")
      {
	cout << error_info << endl;
      }
    else
      {
	cout << branches_info << endl;
      }
  }

  /**
     Implementation of the cmt cvssubpackages
     Get the subdirs of a module that ARE CMT subpackages
   */
  void subpackages (const cmt_string& module)
  {
    cmt_string out;
    
    get_cvs_infos (CmtSystem::getenv ("CMTCVSOFFSET"), (module == "") ? "." : module);
    
    if (error_info != "")
      {
	cout << error_info << endl;
      }
    else
      {
	cout << subpackages_info << endl;
      }
  }
  
  /**
     Implementation of the cmt cvssubrojects
     Get the subdirs of a module that ARE CMT projects
   */
  void subprojects (const cmt_string& module)
  {
    cmt_string out;
    
    get_cvs_infos (CmtSystem::getenv ("CMTCVSOFFSET"), (module == "") ? "." : module);
    
    if (error_info != "")
      {
	cout << error_info << endl;
      }
    else
      {
	cout << subprojects_info << endl;
      }
  }

  /**
     Implementation of the cmt checkout
     Parse the arguments
     Then call do_checkout for each argument
   */
  void checkout (const CmtSystem::cmt_string_vector& arguments)
  {
    if (arguments.size () < 1)
      {
	help ();
	return;
      }
    
    if (CmtSystem::getenv ("CVSROOT") == "")
      {
	cout << "# Please set CVSROOT first !" << endl;
	return;
      }
    
    m_home_dir = CmtSystem::pwd ();
    m_checkout_dir = "";
    m_version_dir = "";
    m_cvs_offset = "";

    cmt_string module;
    
    m_recursive      = false;
    need_touch_files = false;
    
    bool need_version_tag = false;
    cmt_string version_tag;
    
    
    bool need_checkout_dir = false;
    bool need_cvs_offset = false;
    bool need_requirements_file = false;
    bool need_version_dir = false;
    
    m_simulation = false;
    //m_verbose = true;
    m_verbose = false;
    
    m_head = true;
    
    m_cvs_offset = CmtSystem::getenv ("CMTCVSOFFSET");
    if (m_cvs_offset != "") 
      {
	m_cvs_offset += "/";
	m_cvs_offset.replace_all ("//", "/");
      }
    
    for (int arg = 0; arg < arguments.size (); arg++)
      {
	const cmt_string& option = arguments[arg];
	
	if (need_version_tag)
	  {
	    need_version_tag = false;
	    
	    if (option == "HEAD")
	      {
		m_head = true;
	      }
	    else
	      {
		version_tag = option;
	      }
	  }
	else if (need_checkout_dir)
	  {
	    need_checkout_dir = false;
	    m_checkout_dir = option;
	  }
	else if (need_version_dir)
	  {
	    need_version_dir = false;
	    m_version_dir = option;
	  }
	else if (need_cvs_offset)
	  {
	    need_cvs_offset = false;
	    m_cvs_offset = option;
	    m_cvs_offset += '/';
	    m_cvs_offset.replace_all ("//", "/");
	  }
	else if (need_requirements_file)
	  {
	    need_requirements_file = false;
	    m_head = false;
	    checkout_from_requirements (option);
	  }
	else
	  {
	    if (option == "-R")
	      {
		m_recursive = true;
	      }
	    else if (option == "-t")
	    {   
	       need_touch_files = true;
	    }  
	    else if (option == "-l")
	      {
		m_recursive = false;
	      }
	    else if (option == "-r")
	      {
		need_version_tag = true;
		m_head = false;
	      }
	    else if (option == "-d")
	      {
		need_checkout_dir = true;
	      }
	    else if (option == "-o")
	      {
		need_cvs_offset = true;
	      }
	    else if (option == "-n")
	      {
		m_simulation = true;
	      }
	    else if (option == "-v")
	      {
		m_verbose = true;
	      }
	    else if (option == "-vd")
	      {
		need_version_dir = true;
	      }
	    else if (option == "-requirements")
	      {
		need_requirements_file = true;
	      }
	    else if (option == "--help")
	      {
		help ();
		return;
	      }
	    else if (option[0] == '-')
	      {
		help ();
		return;
	      }
	    else
	      {
		do_checkout_phase1 (option, m_version_dir, version_tag);
	      }
	  }
      }
  }

private:

  bool m_recursive;
  bool need_touch_files;
  bool m_head;
  bool m_verbose;
  bool m_simulation;

  cmt_string m_home_dir;
  cmt_string m_checkout_dir;
  cmt_string m_version_dir;
  cmt_string m_cvs_offset;

  cmt_string m_protocol_level;
  cmt_string m_last_module;
  cmt_string m_last_cvs_infos;
  cmt_string structure_info;
  cmt_string error_info;
  cmt_string tags_top_info;
  cmt_string tags_info;
  cmt_string cvsversions_top_info;
  cmt_string cvsversions_info;
  cmt_string branches_info;
  cmt_string subpackages_info;
  cmt_string subprojects_info;
};

//--------------------------------------------------------------------

void Grep::begin ()
{
  m_result = "";
}

void Grep::filter (const cmt_string& line)
{
    //if (CmtSystem::testenv ("CMTTESTAWK")) cout << "Grep::filter" << endl;

  if (m_result != "") m_result += " ";
  m_result += line;
}

const cmt_string& Grep::result () const
{
  return (m_result);
}

//--------------------------------------------------------------------

Cut::Cut (int field)
{
  m_field = field;
}

void Cut::begin ()
{
    //if (CmtSystem::testenv ("CMTTESTAWK")) cout << "Cut::begin" << endl;
  m_result = "";
}

void Cut::filter (const cmt_string& line)
{
    //if (CmtSystem::testenv ("CMTTESTAWK")) cout << "Cut::filter" << endl;

  static CmtSystem::cmt_string_vector words;
  
  CmtSystem::split (line, " \t", words);
  
  if (words.size () <= m_field) return;
  
  if (m_result != "") m_result += " ";
  m_result += words[m_field];
}

const cmt_string& Cut::result () const
{
  return (m_result);
}

//--------------------------------------------------------------------
//--------------------------------------------------------------------

History& History::instance ()
{
  static History h;
  return (h);
}

void History::clear ()
{
  m_installed = "";
}

void History::install (const cmt_string& line)
{
  m_installed += "|";
  m_installed += line;
  m_installed += "|";
}

bool History::is_installed (const cmt_string& line)
{
  if (m_installed.find (line) != cmt_string::npos)
    {
      return (true);
    }
  
  return (false);
}

History::History ()
{
}


//--------------------------------------------------------------------

void RecursivePass1::begin ()
{
  m_first = true;
  m_result = "";
}

void RecursivePass1::filter (const cmt_string& line)
{
    //if (CmtSystem::testenv ("CMTTESTAWK")) cout << "RecursivePass1::filter> " 
    //                                          << "line=[" << line << "]" << endl;

  if (line.find ("use CMT") != cmt_string::npos) return;
  if (line.find ("use cmt") != cmt_string::npos) return;
  
  History& h = History::instance ();
  
  if (h.is_installed (line)) return;
  
  CmtSystem::cmt_string_vector words;
  
  CmtSystem::split (line, " \t", words);

  enum
  {
    need_package,
    need_version,
    need_path,
    no_need
  } state = need_package;

  cmt_string package;
  cmt_string version;
  cmt_string path;

  for (int i = 1; i < words.size (); i++)
    {
      const cmt_string& s = words[i];

      if (s[0] == '-') continue;

      switch (state)
        {
          case need_package:
            package = s;
            state = need_version;
            break;
          case need_version:
            version = s;
            state = need_path;
            break;
          case need_path:
            path = s;
            state = no_need;
            break;
        }
    }
  
  if (version.find ("*") != cmt_string::npos)
    {
        /*
      cout << "# ================= Package " << package 
           << " version " << version << " " << path 
           << " has wild cards and will not be considered." << endl;
        */
      return;
    }

  /**
   *  At the first pass, we simply accumulate the not-yet handled
   *  use statements.
   */
  
  m_result += line;
  m_result += "\n";
  
  if (m_first)
    {
      m_first = false;
      cout << "  # --> now propagate cmt checkout to :" << endl;
    }
  
  cout << "  #     " << package << " " << version << " " << path << endl;
}

const cmt_string& RecursivePass1::result () const
{
  return (m_result);
}

//--------------------------------------------------------------------

RecursivePass2::RecursivePass2 (CvsImplementation& cvs) : m_cvs (cvs)
{
}

void RecursivePass2::begin ()
{
}

void RecursivePass2::filter (const cmt_string& line)
{
    //if (CmtSystem::testenv ("CMTTESTAWK")) cout << "RecursivePass2::filter> " 
    //                                          << "line=[" << line << "]" << endl;

  /**
   *   At the second pass, the lines are really handled. Thus
   *   the lines are stored into m_installed so as to avoid
   *   later on re-installation.
   */
  
  History& h = History::instance ();
  
  if (h.is_installed (line)) return;
  
  h.install (line);
  
  CmtSystem::cmt_string_vector words;
  
  CmtSystem::split (line, " \t", words);

  enum
  {
    need_package,
    need_version,
    need_path,
    no_need
  } state = need_package;

  cmt_string package;
  cmt_string version;
  cmt_string path;

  for (int i = 1; i < words.size (); i++)
    {
      const cmt_string& s = words[i];

      if (s[0] == '-') continue;

      switch (state)
        {
          case need_package:
            package = s;
            state = need_version;
            break;
          case need_version:
            version = s;
            state = need_path;
            break;
          case need_path:
            path = s;
            state = no_need;
            break;
        }
    }

  if (version.find ("*") != cmt_string::npos)
    {
        /*
      cout << "# ================= Package " << package 
           << " version " << version << " " << path 
           << " has wild cards and will not be considered." << endl;
        */
    }
  else
    {
      static const cmt_string empty;
      m_cvs.do_checkout_phase2 (path, package, version, empty);
    }
}

//--------------------------------------------------------------------

void RecursivePass3::begin ()
{
  m_first = true;
  m_result = "";
}

void RecursivePass3::filter (const cmt_string& line)
{
  History& h = History::instance ();
  
  if (h.is_installed (line)) return;
  
  CmtSystem::cmt_string_vector words;
  
  CmtSystem::split (line, " \t", words);

  enum
  {
    need_project,
    need_version,
    need_tag,
    no_need
  } state = need_project;

  cmt_string project;
  cmt_string version;
  cmt_string tag;

  for (int i = 1; i < words.size (); i++)
    {
      const cmt_string& s = words[i];

      switch (state)
        {
          case need_project:
            project = s;
            state = need_version;
            break;
          case need_version:
            version = s;
            state = need_tag;
            break;
          case need_tag:
            tag = s;
            state = no_need;
            break;
        }
    }
  
  if (version.find ("*") != cmt_string::npos)
    {
        /*
      cout << "# ================= Project " << project 
           << " version " << version << " " << path 
           << " has wild cards and will not be considered." << endl;
        */
      return;
    }

  /**
   *  At the first pass, we simply accumulate the not-yet handled
   *  use statements.
   */
  
  m_result += line;
  m_result += "\n";
  
  if (m_first)
    {
      m_first = false;
      cout << "  # --> now propagate cmt checkout to :" << endl;
    }
  
  cout << "  #     " << project << " " << version << " " << tag << endl;
}

const cmt_string& RecursivePass3::result () const
{
  return (m_result);
}

//--------------------------------------------------------------------

RecursivePass4::RecursivePass4 (CvsImplementation& cvs) : m_cvs (cvs)
{
}

void RecursivePass4::begin ()
{
}

void RecursivePass4::filter (const cmt_string& line)
{
    //if (CmtSystem::testenv ("CMTTESTAWK")) cout << "RecursivePass4::filter> " 
    //                                          << "line=[" << line << "]" << endl;

  /**
   *   At the second pass, the lines are really handled. Thus
   *   the lines are stored into m_installed so as to avoid
   *   later on re-installation.
   */
  
  History& h = History::instance ();
  
  if (h.is_installed (line)) return;
  
  h.install (line);
  
  CmtSystem::cmt_string_vector words;
  
  CmtSystem::split (line, " \t", words);

  enum
  {
    need_project,
    need_version,
    need_tag,
    no_need
  } state = need_project;

  cmt_string project;
  cmt_string version;
  cmt_string tag;

  for (int i = 1; i < words.size (); i++)
    {
      const cmt_string& s = words[i];

      switch (state)
        {
          case need_project:
            project = s;
            state = need_version;
            break;
          case need_version:
            version = s;
            state = need_tag;
            break;
          case need_tag:
            tag = s;
            state = no_need;
            break;
        }
    }

  if (version.find ("*") != cmt_string::npos)
    {
        /*
      cout << "# ================= Project " << project 
           << " version " << version
           << " has wild cards and will not be considered." << endl;
        */
    }
  else
    {
      static const cmt_string empty;
      m_cvs.do_checkout_phase2 (empty, project, version, tag);
    }
}

//--------------------------------------------------------------------

void Cvs::tags (const CmtSystem::cmt_string_vector& arguments)
{
  CvsImplementation cvs;

  cvs.tags (arguments);
}

void Cvs::branches (const cmt_string& module)
{
  CvsImplementation cvs;

  cvs.branches (module);
}

void Cvs::subpackages (const cmt_string& module)
{
  CvsImplementation cvs;

  cvs.subpackages (module);
}

void Cvs::subprojects (const cmt_string& module)
{
  CvsImplementation cvs;

  cvs.subprojects (module);
}

void Cvs::checkout (const CmtSystem::cmt_string_vector& arguments)
{
  CvsImplementation cvs;

  cvs.checkout (arguments);
}

