#!/usr/bin/perl -w
#**************************************************************
#  
#  Licensed to the Apache Software Foundation (ASF) under one
#  or more contributor license agreements.  See the NOTICE file
#  distributed with this work for additional information
#  regarding copyright ownership.  The ASF licenses this file
#  to you under the Apache License, Version 2.0 (the
#  "License"); you may not use this file except in compliance
#  with the License.  You may obtain a copy of the License at
#  
#    http://www.apache.org/licenses/LICENSE-2.0
#  
#  Unless required by applicable law or agreed to in writing,
#  software distributed under the License is distributed on an
#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
#  KIND, either express or implied.  See the License for the
#  specific language governing permissions and limitations
#  under the License.
#  
#**************************************************************



#*************************************************************************
#
# cws.pl   - wrap common childworkspace operations
#
use strict;
use Getopt::Long;
use File::Basename;
use File::Path;
use File::Copy; 
use Cwd;
use Benchmark;

#### module lookup
my @lib_dirs;
BEGIN {
    if ( !defined($ENV{SOLARENV}) ) {
        die "No environment found (environment variable SOLARENV is undefined)";
    }
    push(@lib_dirs, "$ENV{SOLARENV}/bin/modules");
}
use lib (@lib_dirs);

use Cws;

#### script id #####

( my $script_name = $0 ) =~ s/^.*\b(\w+)\.pl$/$1/;

#### globals ####

# TODO: replace dummy vales with actual source_config migration milestone
my $ooo320_source_config_milestone = 'm999';

# valid command with possible abbreviations
my @valid_commands = (  
                        'help', 'h', '?',
                        'create', 
                        'fetch',  'f', 
                        'query', 'q',
                        'task', 't',
                        'eisclone',
                        'setcurrent'
                     );

# list the valid options to each command
my %valid_options_hash = ( 
                            'help'       => ['help'],
                            'create'     => ['help', 'milestone', 'migration', 'hg'],
                            'fetch'      => ['help', 'milestone', 'childworkspace','platforms','noautocommon',
                                            'quiet', 'onlysolver', 'additionalrepositories'],
                            'query'      => ['help', 'milestone','masterworkspace','childworkspace'],
                            'task'       => ['help'],
                            'setcurrent' => ['help', 'milestone'],
                            'eisclone'   => ['help']
                         );

my %valid_commands_hash;
for (@valid_commands) {
    $valid_commands_hash{$_}++;
}

#  set by --debug switch
my $debug = 0;
#  set by --profile switch
my $profile = 0;


#### main ####

my ($command, $args_ref, $options_ref) = parse_command_line();
dispatch_command($command, $args_ref, $options_ref);
exit(0);

#### subroutines ####

# Parses the command line. does prelimiary argument and option verification
sub parse_command_line
{
    if (@ARGV == 0) {
        usage();
        exit(1);
    }

    my %options_hash;
    Getopt::Long::Configure ("no_auto_abbrev", "no_ignorecase");
    my $success = GetOptions(\%options_hash, 'milestone|m=s', 
                                             'masterworkspace|master|M=s', 
                                             'hg',
                                             'migration',
                                             'childworkspace|child|c=s',
                                             'debug',
                                             'profile',
                                             'commit|C',
                                             'platforms|p=s',
                                             'additionalrepositories|r=s',
                                             'noautocommon|x=s',
                                             'onlysolver|o',
                                             'quiet|q',
                                             'help|h'
                            );

    my $command = shift @ARGV;

    if (!exists $valid_commands_hash{$command}) {
        print_error("Unkown command: '$command'\n");
        usage();
        exit(1);
    }

    if ($command eq 'h' || $command eq '?') {
        $command = 'help';
    }
    elsif ($command eq 'f') {
        $command = 'fetch';
    }
    elsif ($command eq 'q') {
        $command = 'query';
    }
    elsif ($command eq 't') {
        $command = 'task';
    }

    # An unkown option might be accompanied with a valid command.
    # Show the command specific help
    if ( !$success ) {
        do_help([$command])
    }

    verify_options($command, \%options_hash);
    return ($command, \@ARGV, \%options_hash);
}

# Verify options against the valid options list.
sub verify_options
{
    my $command     = shift;
    my $options_ref = shift;

    my $valid_command_options_ref = $valid_options_hash{$command};

    my %valid_command_options_hash;
    foreach (@{$valid_command_options_ref}) {
        $valid_command_options_hash{$_}++;
    }

    # check all specified options against the valid options for the sub command
    foreach (keys %{$options_ref}) {
        if ( /debug/ ) {
            $debug = 1;
            next;
        }
        if ( /profile/ ) {
            $profile = 1;
            next;
        }
        if (!exists $valid_command_options_hash{$_}) {
            print_error("can't use option '--$_' with subcommand '$command'.", 1);
        }
    }

}

# Dispatches to the do_xxx() routines depending on command.
sub dispatch_command
{
    my $command     = shift;
    my $args_ref    = shift;
    my $options_ref = shift;

    no strict 'refs';
    &{"do_".$command}($args_ref, $options_ref);
}

# Returns the global cws object.
BEGIN {
my $the_cws;

    sub get_this_cws {
        if (!defined($the_cws)) {
            $the_cws = Cws->new();
            return $the_cws;
        }
        else {
            return $the_cws;
        }
    }
}

# Returns a list of the master workspaces.
sub get_master_workspaces
{
    my $cws = get_this_cws();
    my @masters = $cws->get_masters();

    return wantarray ? @masters : \@masters;
}

# Checks if master argument is a valid MWS name.
BEGIN {
    my %master_hash;

    sub is_master
    {
        my $master_name = shift;

        if (!%master_hash) {
            my @masters = get_master_workspaces();
            foreach (@masters) {
                $master_hash{$_}++;
            }
        }
        return exists $master_hash{$master_name} ? 1 : 0;
    }
}

# Fetches the current CWS from environment, returns a Cws object
sub get_cws_from_environment
{
    my $child  = $ENV{CWS_WORK_STAMP};
    my $master = $ENV{WORK_STAMP};

    if ( !$child ) {
        print_error("Environment variable CWS_WORK_STAMP is not set. Please set it to your CWS name.", 2);
    }

    if ( !$master ) {
        print_error("Environment variable WORK_STAMP is not set. Please set it to the MWS name.", 2);
    }

    my $cws = get_this_cws();
    $cws->child($child);
    $cws->master($master);

    # Check if we got a valid child workspace.
    my $id = $cws->eis_id();
    if ( $debug ) {
        print STDERR "CWS-DEBUG: ... master: $master, child: $child, $id\n";
    }
    if ( !$id ) {
        print_error("Child workspace $child for master workspace $master not found in EIS database.", 2);
    }
    return ($cws);
}

# Fetches the CWS by name, returns a Cws object
sub get_cws_by_name
{
    my $child  = shift;

    my $cws = get_this_cws();
    $cws->child($child);

    # Check if we got a valid child workspace.
    my $id = $cws->eis_id();
    if ( $debug ) {
        print STDERR "CWS-DEBUG: child: $child, $id\n";
    }
    if ( !$id ) {
        print_error("Child workspace $child not found in EIS database.", 2);
    }

    # Update masterws part of Cws object.
    my $masterws = $cws->get_mws();
    if ( $cws->master() ne $masterws ) {
        # can this still happen?
        if ( $debug ) {
            print STDERR "CWS-DEBUG: get_cws_by_name(): fixup of masterws in cws object detected\n";
        }
        $cws->master($masterws);
    }
    return ($cws);
}

# Register child workspace with eis.
sub register_child_workspace
{
    my $cws          = shift;
    my $scm          = shift;
    my $is_promotion = shift;

    my $milestone = $cws->milestone();
    my $child     = $cws->child();
    my $master    = $cws->master();

    # TODO: introduce a EIS_USER in the configuration, which should be used here
    my $config = CwsConfig->new();
    my $vcsid  = $config->vcsid();
    # TODO: there is no real need for socustom anymore, should go ASAP 
    my $socustom = $config->sointernal();

    if ( !$vcsid ) {
        if ( $socustom ) {
            print_error("Can't determine owner for CWS '$child'. Please set VCSID environment variable.", 11);
        } 
        else {
            print_error("Can't determine owner for CWS '$child'. Please set CVS_ID entry in \$HOME/.cwsrc.", 11);
        }
    }

    if ( $is_promotion ) {
        my $rc = $cws->set_scm($scm);
        if ( !$rc ) {
            print_error("Failed to set the SCM property '$scm' on child workspace '$child'.\nContact EIS administrator!\n", 12);
        }

        $rc = $cws->promote($vcsid, "");

        if ( !$rc ) {
            print_error("Failed to promote child workspace '$child' to status 'new'.\n", 12);
        }
        else {
            print "\n***** Successfully ***** promoted child workspace '$child' to status 'new'.\n";
            print "Milestone: '$milestone'.\n";
        }
    }
    else {

        my $eis_id = $cws->register($vcsid, "");

        if ( !defined($eis_id) ) {
            print_error("Failed to register child workspace '$child' for master '$master'.", 12);
        }
        else {
            my $rc = $cws->set_scm($scm);
            if ( !$rc ) {
                print_error("Failed to set the SCM property '$scm' on child workspace '$child'.\nContact EIS administrator!\n", 12);
            }
            print "\n***** Successfully ***** registered child workspace '$child'\n";
            print "for master workspace '$master' (milestone '$milestone').\n";
            print "Child workspace Id: $eis_id.\n";
        }
    }
    return 0;
}

sub print_time_elapsed
{
    my $t_start = shift;
    my $t_stop  = shift;

    my $time_diff = timediff($t_stop, $t_start);
    print_message("... finished in " . timestr($time_diff));
}

sub hgrc_append_push_path_and_hooks
{
    my $target     = shift;
    my $cws_source = shift;

    $cws_source =~ s/http:\/\//ssh:\/\/hg@/;
    if ( $debug ) {
        print STDERR "CWS-DEBUG: hgrc_append_push_path_and_hooks(): default-push path: '$cws_source'\n";
    }
    if ( !open(HGRC, ">>$target/.hg/hgrc") ) {
        print_error("Can't append to hgrc file of repository '$target'.\n", 88);
    }
    print HGRC "default-push = " . "$cws_source\n";
    print HGRC "[extensions]\n";
    print HGRC "hgext.win32text=\n";
    print HGRC "[hooks]\n";
    print HGRC "# Reject commits which would introduce windows-style CR/LF files\n";
    print HGRC "pretxncommit.crlf = python:hgext.win32text.forbidcrlf\n";
    close(HGRC);
}

sub hg_clone_cws_or_milestone
{
    my $rep_type             = shift;
    my $cws                  = shift;
    my $target               = shift;
    my $clone_milestone_only = shift;

    my ($hg_local_source, $hg_lan_source, $hg_remote_source);
    my $config = CwsConfig->new();
    
    $hg_local_source = $config->get_hg_source(uc $rep_type, 'LOCAL');
    $hg_lan_source = $config->get_hg_source(uc $rep_type, 'LAN');
    $hg_remote_source = $config->get_hg_source(uc $rep_type, 'REMOTE');

    my $masterws = $cws->master();
    my ($master_local_source, $master_lan_source);
    
    $master_local_source = "$hg_local_source/" . $masterws;
    $master_lan_source = "$hg_lan_source/" . $masterws;

    my $milestone_tag;
    if ( $clone_milestone_only ) {
        $milestone_tag = uc($masterws) . '_' . $clone_milestone_only;
    }
    else {
        my @tags = $cws->get_tags();
        $milestone_tag = $tags[3];
    }

    if ( $debug ) {
        print STDERR "CWS-DEBUG: master_local_source: '$master_local_source'\n";
        print STDERR "CWS-DEBUG: master_lan_source: '$master_lan_source'\n";
        if ( !-d $master_local_source ) {
            print STDERR "CWS-DEBUG: not a directory '$master_local_source'\n";
        }
    }
    
    my $pull_from_remote = 0;
    my $cws_remote_source;
    if ( !$clone_milestone_only ) {
        if ($rep_type eq "ooo" || $rep_type eq "so")
        {
            $cws_remote_source = "$hg_remote_source/cws/" . $cws->child();
        }
        # e.g. cws_l10n 
        else
        {
            $cws_remote_source = "$hg_remote_source/cws_".$rep_type."/" . $cws->child();
        }

        # The outgoing repository might not yet be available. Which is not
        # an error. Since pulling from the cws outgoing URL results in an ugly
        # and hardly understandable error message, we check for availibility
        # first. TODO: incorporate configured proxy instead of env_proxy. Use
        # a dedicated request and content-type to find out if the repo is there 
        # instead of parsing the content of the page
        print_message("... check availibility of 'outgoing' repository '$cws_remote_source'.");
        require LWP::Simple;
        my $content = LWP::Simple::get($cws_remote_source);
        my $pattern = "<title>cws/". $cws->child();
        my $pattern2 = "<title>cws_".$rep_type."/". $cws->child();
        if ( $content && ($content =~ /$pattern/ || $content =~ /$pattern2/) ) {
            $pull_from_remote = 1;
        }
        else {
            print_message("... 'outgoing' repository '$cws_remote_source' is not accessible/available yet.");
        }
    }
    
    # clone repository (without working tree if we still need to pull from remote)
    my $clone_with_update = !$pull_from_remote;
    hg_clone_repository($master_local_source, $master_lan_source, $target, $milestone_tag, $clone_with_update);

    # now pull from the remote cws outgoing repository if its already available
    if ( $pull_from_remote ) {
        hg_remote_pull_repository($cws_remote_source, $target);
    }

    # if we fetched a CWS adorn the result with push-path and hooks
    if ( $cws_remote_source ) {
        hgrc_append_push_path_and_hooks($target, $cws_remote_source);
    }

    # update the result if necessary
    if ( !$clone_with_update ) {
        hg_update_repository($target);
    }

}

sub hg_clone_repository
{
    my $local_source    = shift;
    my $lan_source    = shift;
    my $dest          = shift;
    my $milestone_tag = shift;
    my $update        = shift;

    my $t1 = Benchmark->new();
    my $source;
    my $clone_option = $update ? '' : '-U ';
    if ( -d $local_source && can_use_hardlinks($local_source, $dest) ) {
        $source = $local_source;
        if ( !hg_milestone_is_latest_in_repository($local_source, $milestone_tag) ) {
                $clone_option .= "-r $milestone_tag";
        }
        print_message("... clone LOCAL repository '$local_source' to '$dest'");
    }
    else {
        $source = $lan_source;
        $clone_option .= "-r $milestone_tag";
        print_message("... clone LAN repository '$lan_source' to '$dest'");
    }
    hg_clone($source, $dest, $clone_option);
    
    my $t2 = Benchmark->new();
    print_time_elapsed($t1, $t2) if $profile;
}

sub hg_remote_pull_repository
{
    my $remote_source = shift;
    my $dest          = shift;

    my $t1 = Benchmark->new();
    print_message("... pull from REMOTE repository '$remote_source' to '$dest'");
    hg_pull($dest, $remote_source);
    my $t2 = Benchmark->new();
    print_time_elapsed($t1, $t2) if $profile;
}

sub hg_update_repository
{
    my $dest          = shift;

    my $t1 = Benchmark->new();
    print_message("... update repository '$dest'");
    hg_update($dest);
    my $t2 = Benchmark->new();
    print_time_elapsed($t1, $t2) if $profile;
}

sub hg_milestone_is_latest_in_repository
{
    my $repository = shift;
    my $milestone_tag = shift;

    # Our milestone is the lastest thing in the repository
    # if the parent of the repository tip is adorned
    # with the milestone tag.
    my $tags_of_parent_of_tip = hg_parent($repository, 'tip', "--template='{tags}\\n'");
    if ( $tags_of_parent_of_tip =~ /\b$milestone_tag\b/ ) {
        return 1;
    }
    return 0;
}

# Check if clone source and destination are on the same filesystem,
# in that case hg clone can employ hard links.
sub can_use_hardlinks
{
    my $source = shift;
    my $dest = shift;

    if ( $^O eq 'cygwin' ) {
        # no hard links on windows
        return 0;
    }
    # st_dev is the first field return by stat()
    my @stat_source = stat($source);
    my @stat_dest = stat(dirname($dest));

    if ( $debug ) {
        my $source_result = defined($stat_source[0]) ? $stat_source[0] : 'stat failed';
        my $dest_result = defined($stat_dest[0]) ? $stat_dest[0] : 'stat failed';
        print STDERR "CWS-DEBUG: can_use_hardlinks(): source device: '$stat_source[0]', destination device: '$stat_dest[0]'\n";
    }
    if ( defined($stat_source[0]) && defined($stat_dest[0]) && $stat_source[0] == $stat_dest[0] ) {
        return 1;
    }
    return 0;
}

sub query_cws
{
    my $query_mode = shift;
    my $options_ref = shift;
    # get master and child workspace
    my $masterws  = exists $options_ref->{'masterworkspace'} ? uc($options_ref->{'masterworkspace'}) : $ENV{WORK_STAMP};
    my $childws   = exists $options_ref->{'childworkspace'} ? $options_ref->{'childworkspace'} : $ENV{CWS_WORK_STAMP};
    my $milestone = exists $options_ref->{'milestone'} ? $options_ref->{'milestone'} : 'latest';

    if ( !defined($masterws) && $query_mode ne 'masters') {
        print_error("Can't determine master workspace environment.\n", 30);
    }
    
    if ( ($query_mode eq 'integratedinto' || $query_mode eq 'incompatible' || $query_mode eq 'taskids' || $query_mode eq 'status' || $query_mode eq 'current' || $query_mode eq 'owner' || $query_mode eq 'qarep' || $query_mode eq 'issubversion' || $query_mode eq 'ispublic' || $query_mode eq 'build') && !defined($childws) ) {
        print_error("Can't determine child workspace environment.\n", 30);
    }

    my $cws = Cws->new();
    if ( defined($childws) ) {
        $cws->child($childws);
    }
    if ( defined($masterws) ) {
        $cws->master($masterws);
    }

    no strict;
    &{"query_".$query_mode}($cws, $milestone);
    return;
}

sub query_integratedinto
{
    my $cws = shift;

    if ( is_valid_cws($cws) ) {
        my $milestone = $cws->get_milestone_integrated();
        print_message("Integrated into:");
        print defined($milestone) ? "$milestone\n" : "unkown\n";
    }
    return;
} 
    
sub query_incompatible
{
    my $cws = shift;

    if ( is_valid_cws($cws) ) {
        my @modules = $cws->incompatible_modules();
        print_message("Incompatible Modules:");
        foreach (@modules) {
            if ( defined($_) ) {
                print "$_\n";
            }
        }
    }
    return;
} 
    
sub query_taskids
{
    my $cws = shift;

    if ( is_valid_cws($cws) ) {
        my @taskids = $cws->taskids();
        print_message("Task ID(s):");
        foreach (@taskids) {
            if ( defined($_) ) {
                print "$_\n";
            }
        }
    }
    return;
}

sub query_status
{
    my $cws = shift;

    if ( is_valid_cws($cws) ) {
        my $status = $cws->get_approval();
        if ( !$status ) {
            print_error("Internal error: can't get approval status.", 3);
        } else {
            print_message("Approval status:");
            print "$status\n";
        }
    }
    return;
}

sub query_scm
{
    my $cws = shift;
    my $masterws = $cws->master();
    my $childws  = $cws->child();

    if ( is_valid_cws($cws) ) {
        my $scm = $cws->get_scm();
        if ( !defined($scm) ) {
            print_error("Internal error: can't retrieve scm info.", 3);
        } else {
                print_message("Child workspace uses '$scm'.");
        }
    }
    return;
}

sub query_ispublic
{
    my $cws = shift;
    my $masterws = $cws->master();
    my $childws  = $cws->child();

    if ( is_valid_cws($cws) ) {
        my $ispublic = $cws->get_public_flag();
        if ( !defined($ispublic) ) {
            print_error("Internal error: can't get isPublic flag.", 3);
        } else {
            if ( $ispublic==1 ) {
                print_message("Child workspace is public");
            } else {
                print_message("Child workspace is internal");
            }
        }
    }

    return;
}

sub query_current
{
    my $cws = shift;

    if ( is_valid_cws($cws) ) {
        my $milestone = $cws->milestone();
        if ( !$milestone ) {
            print_error("Internal error: can't get current milestone.", 3);
        } else {
            print_message("Current milestone:");
            print "$milestone\n";
        }
    }
    return;
}

sub query_owner
{
    my $cws = shift;

    if ( is_valid_cws($cws) ) {
        my $owner = $cws->get_owner();
        print_message("Owner:");
        if ( !$owner ) {
            print "not set\n" ;
        } else {
            print "$owner\n";
        }
    }
    return;
}

sub query_qarep
{
    my $cws = shift;

    if ( is_valid_cws($cws) ) {
        my $qarep = $cws->get_qarep();
        print_message("QA Representative:");
        if ( !$qarep ) {
            print "not set\n" ;
        } else {
            print "$qarep\n";
        }
    }
    return;
}


sub query_build
{
    my $cws = shift;

    if ( is_valid_cws($cws) ) {
        my $build = $cws->get_build();
        print_message("Build:");
        if ( $build ) {
            print "$build\n";
        }
    }
    return;
}

sub query_latest
{
    my $cws = shift;

    my $masterws = $cws->master();
    my $latest = $cws->get_current_milestone($masterws);


    if ( $latest ) {
        print_message("Master workspace '$masterws':");
        print_message("Latest milestone available for update:");
        print "$masterws $latest\n";
    }
    else {
        print_error("Can't determine latest milestone of '$masterws' available for update.", 3);
    }

    return;
}

sub query_masters
{
    my $cws = shift;

    my @mws = $cws->get_masters();
    my $list="";

    if ( @mws ) {
        foreach (@mws) {
            if ( $list ne "" ) {
                $list .= ", ";
            }
            $list .= $_;
        }
        print_message("Master workspaces available: $list");
    }
    else {
        print_error("Can't determine masterworkspaces.", 3);
    }

    return;
}

sub query_milestones
{
    my $cws = shift;
    my $masterws = $cws->master();

    my @milestones = $cws->get_milestones($masterws);
    my $list="";

    if ( @milestones ) {
        foreach (@milestones) {
            if ( $list ne "" ) {
                $list .= ", ";
            }
            $list .= $_;
        }
        print_message("Master workspace '$masterws':");
        print_message("Milestones known on Master: $list");
    }
    else {
        print_error("Can't determine milestones of '$masterws'.", 3);
    }

    return;
}

sub query_ispublicmaster
{
    my $cws = shift;
    my $masterws = $cws->master();

    my $ispublic = $cws->get_publicmaster_flag();
    my $list="";

    if ( defined($ispublic) ) {
        print_message("Master workspace '$masterws':");
        if ( !defined($ispublic) ) {
            print_error("Internal error: can't get isPublicMaster flag.", 3);
        } else {
            if ( $ispublic==1 ) {
                print_message("Master workspace is public");
            } else {
                print_message("Master workspace is internal");
            }
        }
    }
    else {
        print_error("Can't determine isPublicMaster flag of '$masterws'.", 3);
    }

    return;
}

sub query_buildid
{
    my $cws       = shift;
    my $milestone = shift;

    my $masterws = $cws->master();
    if ( $milestone eq 'latest' ) {
        $milestone = $cws->get_current_milestone($masterws);
    }

    if ( !$milestone ) {
        print_error("Can't determine latest milestone of '$masterws'.", 3);
    }

    if ( !$cws->is_milestone($masterws, $milestone) ) {
        print_error("Milestone '$milestone' is no a valid milestone of '$masterws'.", 3);
    }

    my $buildid = $cws->get_buildid($masterws, $milestone);


    if ( $buildid ) {
        print_message("Master workspace '$masterws':");
        print_message("BuildId for milestone '$milestone':");
        print("$buildid\n");
    }

    return;
}

sub query_integrated
{
    my $cws       = shift;
    my $milestone = shift;

    my $masterws = $cws->master();
    if ( $milestone eq 'latest' ) {
        $milestone = $cws->get_current_milestone($masterws);
    }

    if ( !$milestone ) {
        print_error("Can't determine latest milestone of '$masterws'.", 3);
    }

    if ( !$cws->is_milestone($masterws, $milestone) ) {
        print_error("Milestone '$milestone' is no a valid milestone of '$masterws'.", 3);
    }

    my @integrated_cws = $cws->get_integrated_cws($masterws, $milestone);


    if ( @integrated_cws ) {
        print_message("Master workspace '$masterws':");
        print_message("Integrated CWSs for milestone '$milestone':");
        foreach (@integrated_cws) {
            print "$_\n";
        }
    }

    return;
}

sub query_approved
{
    my $cws       = shift;

    my $masterws = $cws->master();

    my @approved_cws = $cws->get_cws_with_state($masterws, 'approved by QA');

    if ( @approved_cws ) {
        print_message("Master workspace '$masterws':");
        print_message("CWSs approved by QA:");
        foreach (@approved_cws) {
            print "$_\n";
        }
    }

    return;
}

sub query_nominated
{
    my $cws       = shift;

    my $masterws = $cws->master();

    my @nominated_cws = $cws->get_cws_with_state($masterws, 'nominated');

    if ( @nominated_cws ) {
        print_message("Master workspace '$masterws':");
        print_message("Nominated CWSs:");
        foreach (@nominated_cws) {
            print "$_\n";
        }
    }

    return;
}

sub query_ready
{
    my $cws       = shift;

    my $masterws = $cws->master();

    my @ready_cws = $cws->get_cws_with_state($masterws, 'ready for QA');

    if ( @ready_cws ) {
        print_message("Master workspace '$masterws':");
        print_message("CWSs ready for QA:");
        foreach (@ready_cws) {
            print "$_\n";
        }
    }

    return;
}

sub query_new
{
    my $cws       = shift;

    my $masterws = $cws->master();

    my @ready_cws = $cws->get_cws_with_state($masterws, 'new');

    if ( @ready_cws ) {
        print_message("Master workspace '$masterws':");
        print_message("CWSs with state 'new':");
        foreach (@ready_cws) {
            print "$_\n";
        }
    }

    return;
}

sub query_planned
{
    my $cws       = shift;

    my $masterws = $cws->master();

    my @ready_cws = $cws->get_cws_with_state($masterws, 'planned');

    if ( @ready_cws ) {
        print_message("Master workspace '$masterws':");
        print_message("CWSs with state 'planned':");
        foreach (@ready_cws) {
            print "$_\n";
        }
    }

    return;
}

sub is_valid_cws
{
    my $cws = shift;
    
    my $masterws = $cws->master();
    my $childws  = $cws->child();
    # check if we got a valid child workspace
    my $id = $cws->eis_id();
    if ( !$id ) {
        print_error("Child workspace '$childws' for master workspace '$masterws' not found in EIS database.", 2);
    }
    print STDERR "Master workspace '$masterws', child workspace '$childws'\n";
    return 1;
}

sub query_release
{
    my $cws = shift;

    if ( is_valid_cws($cws) ) {
        my $release = $cws->get_release();
            print_message("Release target:");
        if ( !$release ) {
            print "not set\n";
        } else {
            print "$release\n";
        }
    }
    return;
}

sub query_due
{
    my $cws = shift;

    if ( is_valid_cws($cws) ) {
        my $due = $cws->get_due_date();
            print_message("Due date:");
        if ( !$due ) {
            print "not set\n";
        } else {
            print "$due\n";
        }
    }
    return;
}

sub query_due_qa
{
    my $cws = shift;

    if ( is_valid_cws($cws) ) {
        my $due_qa = $cws->get_due_date_qa();
            print_message("Due date (QA):");
        if ( !$due_qa ) {
            print "not set\n";
        } else {
            print "$due_qa\n";
        }
    }
    return;
}

sub query_help
{
    my $cws = shift;

    if ( is_valid_cws($cws) ) {
        my $help = $cws->is_helprelevant();
            print_message("Help relevant:");
        if ( !$help ) {
            print "false\n";
        } else {
            print "true\n";
        }
    }
    return;
}

sub query_ui
{
    my $cws = shift;

    if ( is_valid_cws($cws) ) {
        my $help = $cws->is_uirelevant();
            print_message("UI relevant:");
        if ( !$help ) {
            print "false\n";
        } else {
            print "true\n";
        }
    }
    return;
}

sub verify_milestone
{
    my $cws = shift;
    my $qualified_milestone = shift;

    my $invalid = 0;
    my ($master, $milestone);
    $invalid++ if $qualified_milestone =~ /-/;

    if ( $qualified_milestone =~ /:/ ) {
        ($master, $milestone) = split(/:/, $qualified_milestone);
        $invalid++ unless ( $master && $milestone );
    }
    else {
        $milestone = $qualified_milestone;
    }

    if ( $invalid ) {
        print_error("Invalid milestone", 0);
        usage();
        exit(1);
    }

    $master = $cws->master() if !$master;
    if ( !$cws->is_milestone($master, $milestone) ) {
        print_error("Milestone '$milestone' is not registered with master workspace '$master'.", 21);
    }
    return ($master, $milestone);
}

sub relink_workspace {
    my $linkdir = shift;
    my $restore = shift;

    # The list of obligatorily added modules, build will not work
    # if these are not present. 
    my %added_modules_hash;
    if (defined $ENV{ADDED_MODULES}) {
        for ( split(/\s/, $ENV{ADDED_MODULES}) ) {
            $added_modules_hash{$_}++;
        }
    }

    # clean out pre-existing linkdir
    my $bd = dirname($linkdir);
    if ( !opendir(DIR, $bd) ) {
        print_error("Can't open directory '$bd': $!.", 44);
    }
    my @old_link_dirs = grep { /^src.m\d+/ } readdir(DIR);
    close(DIR);

    if ( @old_link_dirs > 1 ) {
        print_error("Found more than one old link directories:", 0);
        foreach (@old_link_dirs) {
            print STDERR "@old_link_dirs\n";
        }
        if ( $restore ) {
            print_error("Please remove all old link directories but the last one", 67);
        }
    }

    # Originally the extension .lnk indicated a linked module. This turned out to be
    # not an overly smart choice. Cygwin has some heuristics which regards .lnk 
    # files as Windows shortcuts, breaking the build. Use .link instead.
    # When in restoring mode still consider .lnk as link to modules (for old CWSs)
    my $old_link_dir = "$bd/" . $old_link_dirs[0];
    if ( $restore ) {
        if ( !opendir(DIR, $old_link_dir) ) {
            print_error("Can't open directory '$old_link_dir': $!.", 44);
        }
        my @links = grep { !(/\.lnk/ || /\.link/)   } readdir(DIR);
        close(DIR);
        # everything which is not a link to a directory can't be an "added" module
        foreach (@links) {
            next if /^\./;
            my $link = "$old_link_dir/$_";
            if ( -s $link && -d $link ) {
                $added_modules_hash{$_} = 1;
            }
        }
    }
    print_message("... removing '$old_link_dir'");
    rmtree([$old_link_dir], 0);
    
    print_message("... (re)create '$linkdir'");
    if ( !mkdir("$linkdir") ) {
        print_error("Can't create directory '$linkdir': $!.", 44);
    }
    if ( !opendir(DIR, "$bd/ooo") ) {
        print_error("Can't open directory '$bd/sun': $!.", 44);
    }
    my @ooo_top_level_dirs = grep { !/^\./ } readdir(DIR);
    close(DIR);
    if ( !opendir(DIR, "$bd/sun") ) {
        print_error("Can't open directory '$bd/sun': $!.", 44);
    }
    my @so_top_level_dirs = grep { !/^\./ } readdir(DIR);
    close(DIR);
    my $savedir = getcwd();
    if ( !chdir($linkdir) ) {
        print_error("Can't chdir() to directory '$linkdir': $!.", 44);
    }
    my $suffix = '.link';
    foreach(@ooo_top_level_dirs) {
        if ( $_ eq 'REBASE.LOG' || $_ eq 'REBASE.CONFIG_DONT_DELETE'  ) {
            next;
        }
        my $target = $_;
        if ( -d "../ooo/$_" && !exists $added_modules_hash{$_} ) {
            $target .= $suffix;
        }
        if ( !symlink("../ooo/$_", $target) ) {
            print_error("Can't symlink directory '../ooo/$_ -> $target': $!.", 44);
        }
    }
    foreach(@so_top_level_dirs) {
        if ( $_ eq 'REBASE.LOG' || $_ eq 'REBASE.CONFIG_DONT_DELETE'  ) {
            next;
        }
        my $target = $_;
        if ( -d "../sun/$_" && !exists $added_modules_hash{$_} ) {
            $target .= $suffix;
        }
        if ( !symlink("../sun/$_", $target) ) {
            print_error("Can't symlink directory '../sun/$_ -> $target': $!.", 44);
        }
    }
    if ( !chdir($savedir) ) {
        print_error("Can't chdir() to directory '$linkdir': $!.", 44);
    }
}

sub fetch_external_tarballs
{
    my $source_root_dir = shift;
    my $external_tarballs_source = shift;

    my $ooo_external_file = "$source_root_dir/ooo/ooo.lst";
    my $sun_external_file = "$source_root_dir/sun/sun.lst";
    my $sun_path          = "$source_root_dir/sun";

    my @external_sources_list;
    push(@external_sources_list, read_external_file($ooo_external_file));
    if ( -d $sun_path ) {
        if ( -e $sun_external_file ) {
            push(@external_sources_list, read_external_file($sun_external_file));
        }
        else {
            print_error("Can't find external file list '$sun_external_file'.", 8);
        }
    }
    
    my $ext_sources_dir = "$source_root_dir/ext_sources";
    print_message("Copy external tarballs to '$ext_sources_dir'");
    if ( ! -d $ext_sources_dir) {
        if ( !mkdir($ext_sources_dir) ) {
            print_error("Can't create directory '$ext_sources_dir': $!.", 44);
        }
    }
    foreach (@external_sources_list) {
        if ( ! copy("$external_tarballs_source/$_", $ext_sources_dir) ) {
            print_error("Can't copy file '$external_tarballs_source' -> '$ext_sources_dir': $!", 0);
        }
    }
    return;
}

sub read_external_file
{
    my $external_file = shift;

    my @external_sources;
    open(EXT, "<$external_file") or print_error("Can't open file '$external_file' for reading: $!", 98);
    while(<EXT>) {
        if ( !/^http:/ ) {
            chomp;
            push(@external_sources, $_);
        }
    }
    close(EXT);
    return @external_sources;
}

sub update_solver
{
    my $platform      = shift;
    my $source        = shift;
    my $solver        = shift;
    my $milestone     = shift;
    my $source_config = shift;

    my @zip_sub_dirs = ('bin', 'doc', 'idl', 'inc', 'lib', 'par', 'pck', 'pdb', 'pus', 'rdb', 'res', 'xml', 'sdf');
    
    use Archive::Zip qw( :ERROR_CODES :CONSTANTS );

    my $platform_solver = "$solver/$platform";

    if ( -d $platform_solver ) {
        print_message("... removing old solver for platform '$platform'");
        if ( !rmtree([$platform_solver]) ) {
            print_error("Can't remove directory '$platform_solver': $!.", 44);
        }
    }

    if ( !mkdir("$platform_solver") ) {
        print_error("Can't create directory '$platform_solver': $!.", 44);
    }

    my $platform_source = "$source/$platform/zip.$milestone";
    if ( !opendir(DIR, "$platform_source") ) {
        print_error("Can't open directory '$platform_source': $!.", 44);
    }
    my @zips = grep { /\.zip$/ } readdir(DIR);
    close(DIR);

    my $nzips = @zips;
    print_message("... unzipping $nzips zip archives for platform '$platform'");


    foreach(@zips) {
        my $zip = Archive::Zip->new();
        unless ( $zip->read( "$platform_source/$_" ) == AZ_OK ) {
            print_error("Can't read zip file '$platform_source/$_': $!.", 44);
        }
        # TODO: check for erorrs
        foreach (@zip_sub_dirs) {
            my $extract_destination = $source_config ? "$platform_solver/$_" : "$platform_solver/$_.$milestone";
            unless ( $zip->extractTree($_, $extract_destination) == AZ_OK ) {
                print_error("Can't extract stream from zip file '$platform_source/$_': $!.", 44);
            }
        }
     }
}

# TODO: special provisions for "source_config" migration, remove this 
# some time after migration
sub get_source_config_for_milestone
{
    my $masterws = shift;
    my $milestone = shift;

    my $milestone_sequence_number = extract_milestone_sequence_number($milestone);
    my $ooo320_migration_sequence_number = extract_milestone_sequence_number($ooo320_source_config_milestone);

    my $source_config = 1;
    if ( $masterws eq 'OOO320' ) {
        if ( $milestone_sequence_number < $ooo320_migration_sequence_number ) {
            $source_config = 0; 
        }
    }
    return $source_config;
}

sub extract_milestone_sequence_number
{
    my $milestone = shift;

    my $milestone_sequence_number;
    if ( $milestone =~ /m(\d+)/ ) {
        $milestone_sequence_number = $1;
    }
    else {
        print_error("can't extract milestone sequence number from milestone '$milestone'", 99);
    }
    return $milestone_sequence_number;
}

# Executes the help command.
sub do_help
{
    my $args_ref    = shift;
    my $options_ref = shift;

    if (@{$args_ref} == 0) {
        print STDERR "usage: cws <subcommand> [options] [args]\n";
        print STDERR "Type 'cws help <subcommand>' for help on a specific subcommand.\n";
        print STDERR "\n";
        print STDERR "Available subcommands:\n";
        print STDERR "\thelp (h,?)\n";
        print STDERR "\tcreate\n";
        print STDERR "\tfetch (f)\n";
        print STDERR "\tquery (q)\n";
        print STDERR "\ttask (t)\n";
        print STDERR "\tsetcurrent\n";
        print STDERR "\teisclone *** release engineers only ***\n";
    }

    my $arg = $args_ref->[0];

    if (!defined($arg) || $arg eq 'help') {
        print STDERR "help (h, ?): Describe the usage of this script or its subcommands\n";
        print STDERR "usage: help [subcommand]\n";
    }
    elsif ($arg eq 'create') {
        print STDERR "create: Create a new child workspace\n";
        print STDERR "usage: create [-m milestone] <master workspace> <child workspace>\n";
        print STDERR "\t-m milestone:          Milestone to base the child workspace on. If ommitted the\n";
        print STDERR "\t                       last published milestone will be used.\n";
        print STDERR "\t--milestone milestone: Same as -m milestone.\n";
    }
    elsif ($arg eq 'task') {
        print STDERR "task: Add a task to a child workspace\n";
        print STDERR "usage: task <task id> [task id ...]\n";
    }
    elsif ($arg eq 'query') {
        print STDERR "query: Query child workspace for miscellaneous information\n";
        print STDERR "usage: query [-M master] [-c child] <current|integratedinto|incompatible|owner|qarep|status|taskids>\n";
        print STDERR "       query [-M master] [-c child] <release|due|due_qa|help|ui|ispublic|scm|build>\n";
        print STDERR "       query [-M master] <latest|milestones|ispublicmaster>\n";
        print STDERR "       query  <masters>\n";
        print STDERR "       query [-M master] [-m milestone] <integrated|buildid>\n";
        print STDERR "       query [-M master] <planned|new|approved|nominated|ready>\n";
        print STDERR "\t-M master:\t\toverride MWS specified in environment\n";
        print STDERR "\t-c child:\t\toverride CWS specified in environment\n";
        print STDERR "\t-m milestone:\t\toverride latest milestone with specified one\n";
        print STDERR "\t--master master:\tSame as -M master\t\n";
        print STDERR "\t--child child:\t\tSame -c child\n";
        print STDERR "\t--milestone milestone:\tSame as -m milestone\n";
        print STDERR "Modes:\n";
        print STDERR "\tcurrent\t\tquery current milestone of CWS\n";
        print STDERR "\tincompatible\tquery modules which should be build incompatible\n";
        print STDERR "\towner\t\tquery CWS owner\n";
        print STDERR "\tqarep\t\tquery CWS QA Representative\n";
        print STDERR "\tstatus\t\tquery approval status of CWS\n";
        print STDERR "\ttaskids\t\tquery taskids to be handled on the CWS\n";
        print STDERR "\trelease\t\tquery for target release of CWS\n";
        print STDERR "\tdue\t\tquery for due date of CWS\n";
        print STDERR "\tdue_qa\t\tquery for due date (QA) of CWS\n";
        print STDERR "\thelp\t\tquery if the CWS is help relevant\n";
        print STDERR "\tui\t\tquery if the CWS is UI relevant\n";
        print STDERR "\tbuild\t\tquery build String for CWS\n";
        print STDERR "\tlatest\t\tquery the latest milestone available for resync\n";
        print STDERR "\tbuildid\t\tquery build ID for milestone\n";
        print STDERR "\tintegrated\tquery integrated CWSs for milestone\n";
        print STDERR "\tintegratedinto\tquery milestone which CWS was integrated into\n";
        print STDERR "\tplanned\t\tquery for planned CWSs\n";
        print STDERR "\tnew\t\tquery for new CWSs\n";
        print STDERR "\tapproved\tquery CWSs approved by QA\n";
        print STDERR "\tnominated\tquery nominated CWSs\n";
        print STDERR "\tready\t\tquery CWSs ready for QA\n";
        print STDERR "\tispublic\tquery public flag of CWS\n";
        print STDERR "\tscm\t\tquery Source Control Management (SCM) system used for CWS\n";
        print STDERR "\tmasters\t\tquery available MWS\n";
        print STDERR "\tmilestones\tquery which milestones are know on the given MWS\n";
        print STDERR "\tispublicmaster\tquery public flag of MWS\n";

     }
    elsif ($arg eq 'fetch') {
        print STDERR "fetch: fetch a milestone or CWS\n";
        print STDERR "usage: fetch [-q] [-p platforms] [-r additionalrepositories] [-o] <-m milestone> <workspace>\n";
        print STDERR "usage: fetch [-q] [-p platforms] [-r additionalrepositories] [-o] <-c cws> <workspace>\n";
        print STDERR "usage: fetch [-q] [-x platforms] [-r additionalrepositories] [-o] <-m milestone> <workspace>\n";
        print STDERR "usage: fetch [-q] [-x platforms] [-r additionalrepositories] [-o] <-c cws> <workspace>\n";
        print STDERR "usage: fetch [-q] <-m milestone> <workspace>\n";
        print STDERR "usage: fetch [-q] <-c cws> <workspace>\n";
        print STDERR "\t-m milestone:            Checkout milestone <milestone> to workspace <workspace>\n";
        print STDERR "\t                         Use 'latest' for the for lastest published milestone on the current master\n";
        print STDERR "\t                         For cross master checkouts use the form <MWS>:<milestone>\n";
        print STDERR "\t--milestone milestone:   Same as -m milestone\n";
        print STDERR "\t-c childworkspace:       Checkout CWS <childworkspace> to workspace <workspace>\n";
        print STDERR "\t--child childworkspace:  Same as -c childworkspace\n";
        print STDERR "\t-p platform:             Copy one or more prebuilt platforms 'platform'. \n";
        print STDERR "\t                         Separate multiple platforms with commas.\n";
        print STDERR "\t                         Automatically adds 'common[.pro]' as required.\n";
        print STDERR "\t--platforms platform:    Same as -p\n";
        print STDERR "\t-x platform:             Copy one or more prebuilt platforms 'platform'. \n";
        print STDERR "\t                         Separate multiple platforms with commas.\n";
        print STDERR "\t                         Does not automatically adds 'common[.pro]'.\n";
        print STDERR "\t-r additionalrepositories Checkout additional repositories. \n";
        print STDERR "\t                         Separate multiple repositories with commas.\n";
        print STDERR "\t--noautocommon platform: Same as -x\n";
        print STDERR "\t-o:                      Omit checkout of sources, copy only solver. \n";
        print STDERR "\t--onlysolver:            Same as -o\n";
        print STDERR "\t-q:                      Silence some of the output of the command.\n";
        print STDERR "\t--quiet:                 Same as -q\n";
    }
    elsif ($arg eq 'setcurrent') {
        print STDERR "setcurrent: Set the current milestone for the CWS (only hg based CWSs)\n";
        print STDERR "usage: setcurrent [-m milestone]\n";
        print STDERR "\t-m milestone:           Set milestone to <milestone> to workspace <workspace>\n";
        print STDERR "\t                        Use 'latest' for the for lastest published milestone on the current master\n";
        print STDERR "\t                        For cross master change use the form <MWS>:<milestone>\n";
        print STDERR "\t--milestone milestone:  Same as -m milestone\n";
    }
    else {
        print STDERR "'$arg': unknown subcommand\n";
        exit(1);
    }
    exit(0);
}

# Executes the create command.
sub do_create
{
    my $args_ref    = shift;
    my $options_ref = shift;

    if ( exists $options_ref->{'help'} || @{$args_ref} != 2) {
        do_help(['create']);
    }

    if ( exists $options_ref->{'hg'} ) {
        print_warning("All childworkspaces are now hosted on Mercurial. The switch --hg is obsolete.");
    }

    my $master   = uc $args_ref->[0];
    my $cws_name = $args_ref->[1];

    if (!is_master($master)) {
        print_error("'$master' is not a valid master workspace.", 7);
    }

    # check if cws name fits the convention
    if ( $cws_name !~ /^\w[\w\.\#]*$/ ) {
        print_error("Invalid child workspace name '$cws_name'.\nCws names should consist of alphanumeric characters, preferable all lowercase and starting with a letter.\nThe characters . and # are allowed if they are not the first character.", 7);
    }

    my $cws = get_this_cws();
    $cws->master($master);
    $cws->child($cws_name);

    # check if child workspace already exists
    my $eis_id = $cws->eis_id();
    if ( !defined($eis_id) ) {
        print_error("Connection with EIS database failed.", 8);
    }

    my $is_promotion = 0;
    if ( $eis_id > 0 ) {
        if ( $cws->get_approval() eq 'planned' ) {
            print "Promote child workspace '$cws_name' from 'planned' to 'new'.\n";
            $is_promotion++;
        }
        else {
            print_error("Child workspace '$cws_name' already exists.", 7);
        }
    }
    else {
        # check if child workspace name is still available
        if ( !$cws->is_cws_name_available()) {
            print_error("Child workspace name '$cws_name' is already in use.", 7);
        }
    }

    my $milestone;
    # verify milestone or query latest milestone
    if ( exists $options_ref->{'milestone'} ) {
        $milestone=$options_ref->{'milestone'};
        # check if milestone exists
        if ( !$cws->is_milestone($master, $milestone) ) {
            print_error("Milestone '$milestone' is not registered with master workspace '$master'.", 8);
        }
    }
    else {
        $milestone=$cws->get_current_milestone($cws->master());
    }

    # set milestone
    $cws->milestone($milestone);

    register_child_workspace($cws, 'hg', $is_promotion);

    return;
}

# Executes the fetch command.
sub do_fetch
{
    my $args_ref    = shift;
    my $options_ref = shift;

    my $time_fetch_start = Benchmark->new();
    if ( exists $options_ref->{'help'} || @{$args_ref} != 1) {
        do_help(['fetch']);
    }

    my $milestone_opt = $options_ref->{'milestone'};
    my $additional_repositories_opt = $options_ref->{'additionalrepositories'};
    $additional_repositories_opt = "", if ( !defined $additional_repositories_opt );
    my $child = $options_ref->{'childworkspace'};
    my $platforms = $options_ref->{'platforms'};
    my $noautocommon = $options_ref->{'noautocommon'};
    my $quiet  = $options_ref->{'quiet'}  ? 1 : 0 ;
    my $switch = $options_ref->{'switch'} ? 1 : 0 ;
    my $onlysolver = $options_ref->{'onlysolver'} ? 1 : 0 ;

    if ( !defined($milestone_opt) && !defined($child) ) {
        print_error("Specify one of these options: -m or -c", 0);
        do_help(['fetch']);
    }

    if ( defined($milestone_opt) && defined($child) ) {
        print_error("Options -m and -c are mutally exclusive", 0);
        do_help(['fetch']);
    }

    if ( defined($platforms) && defined($noautocommon) ) {
        print_error("Options -p and -x are mutally exclusive", 0);
        do_help(['fetch']);
    }

    if ( $onlysolver && !(defined($platforms) || defined($noautocommon)) ) {
        print_error("Option '-o' is Only usuable combination with option '-p' or '-x'.", 0);
        do_help(['fetch']);
    }

    my $cws = get_this_cws();
    my $masterws = $ENV{WORK_STAMP};
    if ( !defined($masterws) ) {
        print_error("Can't determine current master workspace: check environment variable WORK_STAMP", 21);
    }
    $cws->master($masterws);
    my $milestone;
    if( defined($milestone_opt) ) {
        if ( $milestone_opt eq 'latest' ) {
            $cws->master($masterws);
            my $latest = $cws->get_current_milestone($masterws);

            if ( !$latest ) {
                print_error("Can't determine latest milestone of master workspace '$masterws'.", 22);
            }
            $milestone = $cws->get_current_milestone($masterws);
        }
        else {
            ($masterws, $milestone) =  verify_milestone($cws, $milestone_opt);
        }
    }
    elsif ( defined($child) ) {
        $cws = get_cws_by_name($child);
        $masterws = $cws->master(); # CWS can have another master than specified in ENV
        $milestone = $cws->milestone();
    }
    else {
        do_help(['fetch']);
    }

    my $config = CwsConfig->new();
    # $so_svn_server is still required to determine if we are in SO environment
    # TODO: change this configuration setting to something more meaningful
    my $so_svn_server = $config->get_so_svn_server();
    my $prebuild_dir = $config->get_prebuild_binaries_location();
    my $external_tarball_source = $prebuild_dir;
    # Check early for platforms so we can bail out before anything time consuming is done
    # in case of a missing platform
    my @platforms;
    if ( defined($platforms) || defined($noautocommon) ) {
        use Archive::Zip; # warn early if module is missing
        if ( !defined($prebuild_dir ) ) {
            print_error("PREBUILD_BINARIES not configured, can't find platform solvers", 99);
        }
        $prebuild_dir = "$prebuild_dir/$masterws";

        if ( defined($platforms) ) {
            @platforms = split(/,/, $platforms);

            my $added_product = 0;
            my $added_nonproduct = 0;
            foreach(@platforms) {
                if ( $_ eq 'common.pro' ) {
                    $added_product = 1;
                    print_warning("'$_' is added automatically to the platform list, don't specify it explicit");
                }
                if ( $_ eq 'common' ) {
                    $added_nonproduct = 1;
                    print_warning("'$_' is added automatically to the platform list, don't specify it explicit");
                }
            }

            # add common.pro/common to platform list
            if ( $so_svn_server ) {
                my $product = 0;
                my $nonproduct = 0;
                foreach(@platforms) {
                    if ( /\.pro$/ ) {
                        $product = 1;
                    }
                    else {
                        $nonproduct = 1;
                    }
                }
                unshift(@platforms, 'common.pro') if ($product && !$added_product);
                unshift(@platforms, 'common') if ($nonproduct && !$added_nonproduct);
            }
        }
        else {
            @platforms = split(/,/, $noautocommon);
        }

        foreach(@platforms) {
            if ( ! -d "$prebuild_dir/$_") {
                print_error("Can't find prebuild binaries for platform '$_'.", 22);
            }
        }

    }

    my $cwsname = $cws->child();
    my $linkdir = $milestone_opt ? "src.$milestone" : "src." . $cws->milestone;

    my $workspace = $args_ref->[0];

    if ( !$onlysolver ) {
        if ( -e $workspace ) {
            print_error("File or directory '$workspace' already exists.", 8);
        }

        my $clone_milestone_only = $milestone_opt ? $milestone : 0;
        if ( defined($so_svn_server) ) {
            if ( !mkdir($workspace) ) {
                print_error("Can't create directory '$workspace': $!.", 8);
            }
            my $work_master = "$workspace/$masterws";
            if ( !mkdir($work_master) ) {
                print_error("Can't create directory '$work_master': $!.", 8);
            }

            my %unique = map { $_ => 1 } split( /,/ , $additional_repositories_opt);
            my @unique_repo_list = keys %unique;

            if (defined($additional_repositories_opt))
            {
                foreach my $repo(@unique_repo_list) 
                {
                    # do not double clone ooo and sun
                    hg_clone_cws_or_milestone($repo, $cws, "$work_master/".$repo, $clone_milestone_only), if $repo ne "ooo" && $repo ne "sun";
                }

            }
            
            hg_clone_cws_or_milestone('ooo', $cws, "$work_master/ooo", $clone_milestone_only); 
            hg_clone_cws_or_milestone('so', $cws, "$work_master/sun", $clone_milestone_only);
 
            if ( get_source_config_for_milestone($masterws, $milestone) ) {
                # write source_config file
                my $source_config_file = "$work_master/source_config";
                if ( !open(SOURCE_CONFIG, ">$source_config_file") ) {
                    print_error("Can't create source_config file '$source_config_file': $!.", 8);
                }
                print SOURCE_CONFIG "[repositories]\n";
                print SOURCE_CONFIG "ooo=active\n";
                print SOURCE_CONFIG "sun=active\n";
                foreach my $repo(@unique_repo_list)
                {
                    print SOURCE_CONFIG $repo."=active\n", if $repo ne "ooo" || $repo ne "sun";
                }
                close(SOURCE_CONFIG);
            }
            else {
                my $linkdir = "$work_master/src.$milestone";
                if ( !mkdir($linkdir) ) {
                    print_error("Can't create directory '$linkdir': $!.", 8);
                }
                relink_workspace($linkdir);
            }
        }
        else {
            hg_clone_cws_or_milestone('ooo', $cws, $workspace, $clone_milestone_only);
        }
    }

    if ( !$onlysolver && defined($external_tarball_source) ) {
        my $source_root_dir = "$workspace/$masterws";
        $external_tarball_source .= "/$masterws/ext_sources";
        if ( -e "$source_root_dir/ooo/ooo.lst" && -d $external_tarball_source ) {
            fetch_external_tarballs($source_root_dir, $external_tarball_source);
        }
    }

    if ( defined($platforms) || defined($noautocommon) ) {
        if ( !-d $workspace ) {
            if ( !mkdir($workspace) ) {
                print_error("Can't create directory '$workspace': $!.", 8);
            }
        }
        my $solver = defined($so_svn_server) ? "$workspace/$masterws" : "$workspace/solver";
        if ( !-d $solver ) {
            if ( !mkdir($solver) ) {
                print_error("Can't create directory '$solver': $!.", 8);
            }
        }
        my $source_config = get_source_config_for_milestone($masterws, $milestone);
        foreach(@platforms) {
            my $time_solver_start = Benchmark->new();
            print_message("... copying platform solver '$_'.");
            update_solver($_, $prebuild_dir, $solver, $milestone, $source_config);
            my $time_solver_stop = Benchmark->new();
            print_time_elapsed($time_solver_start, $time_solver_stop) if $profile;
        }
    }
    my $time_fetch_stop = Benchmark->new();
    my $time_fetch = timediff($time_fetch_stop, $time_fetch_start);
    print_message("cws fetch: total time required " . timestr($time_fetch));
}

sub do_query
{
    my $args_ref    = shift;
    my $options_ref = shift;

    # list of available query modes
    my @query_modes = qw(integratedinto incompatible taskids status latest current owner qarep build buildid integrated approved nominated ready new planned release due due_qa help ui milestones masters scm ispublic ispublicmaster);
    my %query_modes_hash = ();
    foreach (@query_modes) {
        $query_modes_hash{$_}++;
    }

    if ( exists $options_ref->{'help'} || @{$args_ref} != 1) {
        do_help(['query']);
    }
    my $mode = lc($args_ref->[0]);

    # cwquery mode 'state' has been renamed to 'status' to be more consistent
    # with CVS etc. 'state' is still an alias for 'status'
    $mode = 'status' if $mode eq 'state';

    # cwquery mode 'vcs' has been renamed to 'scm' to be more consistent
    # with general use etc. 'vcs' is still an alias for 'scm'
    $mode = 'scm' if $mode eq 'vcs';

    # there will be more query modes over time
    if ( !exists $query_modes_hash{$mode} ) {
        do_help(['query']);
    }
    query_cws($mode, $options_ref);
}

sub do_task
{
    my $args_ref    = shift;
    my $options_ref = shift;

    if ( exists $options_ref->{'help'} ) {
        do_help(['task']);
    }

    # CWS states for which adding tasks are blocked.
    my @states_blocked_for_adding = (
                                        "integrated",
                                        "nominated",
                                        "approved by QA",
                                        "cancelled",
                                        "finished"
                                    );
    my $cws = get_cws_from_environment();

    # register taskids with EIS database;
    # checks taksids for sanity, will notify user
    # if taskid is already registered.
    my $status = $cws->get_approval();

    my $child = $cws->child();
    my $master = $cws->master();

    my @registered_taskids = $cws->taskids();

    # if called without ids to register just query for tasks
    if ( @{$args_ref} == 0 ) {
        print_message("Task ID(s):");
        foreach (@registered_taskids) {
            if ( defined($_) ) {
                print "$_\n";
            }
        }
    }

    if ( !defined($status) ) {
        print_error("Can't determine status of child workspace `$child`.", 20);
    }

    if ( grep($status eq $_, @states_blocked_for_adding) ) {
        print_error("Can't add tasks to child workspace '$child' with state '$status'.", 21);
    }

    # Create hash for easier searching.
    my %registered_taskids_hash = ();
    for (@registered_taskids) {
        $registered_taskids_hash{$_}++;
    }

    my @new_taskids = ();
    foreach (@{$args_ref}) {
        if ( $_ !~ /^([ib]?\d+)$/ ) {
            print_error("'$_' is an invalid task ID.", 22);
        }
        if ( exists $registered_taskids_hash{$1} ) {
            print_warning("Task ID '$_' already registered, skipping.");
            next;
        }
        push(@new_taskids, $_);
    }

    # TODO: introduce a EIS_USER in the configuration, which should be used here
    my $config = CwsConfig->new();
    my $vcsid  = $config->vcsid();
    my $added_taskids_ref = $cws->add_taskids($vcsid, @new_taskids);
    if ( !$added_taskids_ref )  {
        my $taskids_str = join(" ", @new_taskids);
        print_error("Couldn't register taskID(s) '$taskids_str' with child workspace '$child'.", 23);
    }
    my @added_taskids = @{$added_taskids_ref};
    if ( @added_taskids ) {
        my $taskids_str = join(" ", @added_taskids);
        print_message("Registered taskID(s) '$taskids_str' with child workspace '$child'.");
    }
    return;
}

sub do_setcurrent
{
    my $args_ref    = shift;
    my $options_ref = shift;

    if ( exists $options_ref->{'help'} || @{$args_ref} != 0) {
        do_help(['setcurrent']);
    }

    if ( !exists $options_ref->{'milestone'} ) {
        do_help(['setcurrent']);
    }

    my $cws = get_cws_from_environment();
    my $old_masterws = $cws->master();
    my $new_masterws;
    my $new_milestone;

    my $milestone = $options_ref->{'milestone'};
    if ( $milestone eq 'latest' ) {
        my $latest = $cws->get_current_milestone($old_masterws);

        if ( !$latest ) {
            print_error("Can't determine latest milestone of '$old_masterws'.", 22);
        }
        $new_masterws  = $old_masterws;
        $new_milestone = $latest;
    }
    else {
        ($new_masterws, $new_milestone) =  verify_milestone($cws, $milestone);
    }
    
    print_message("... updating EIS database");
    my $push_return = $cws->set_master_and_milestone($new_masterws, $new_milestone);
    # sanity check
    if ( $$push_return[1] ne $new_milestone) {
        print_error("Couldn't push new milestone '$new_milestone' to database", 0);
    }
}

sub do_eisclone
{
    my $args_ref    = shift;
    my $options_ref = shift;

    print_error("not yet implemented.", 2);
}

sub print_message
{
    my $message     = shift;

    print "$message\n";
    return;
}

sub print_warning
{
    my $message     = shift;
    print STDERR "$script_name: ";
    print STDERR "WARNING: $message\n";
    return;
}

sub print_error
{
    my $message     = shift;
    my $error_code  = shift;

    print STDERR "$script_name: ";
    print STDERR "ERROR: $message\n";

    if ( $error_code ) {
        print STDERR "\nFAILURE: $script_name aborted.\n";
        exit($error_code);
    }
    return;
}

sub usage
{
        print STDERR "Type 'cws help' for usage.\n";
}

### HG glue ###

sub hg_clone
{
    my $source  = shift;
    my $dest    = shift;
    my $options = shift;

    if ( $debug ) {
        print STDERR "CWS-DEBUG: ... hg clone: '$source -> $dest', options: '$options'\n";
    }

    # The to be cloned revision might not yet be avaliable. In this case clone
    # the available tip.
    my @result = execute_hg_command(0, 'clone', $options, $source, $dest);
    if ( defined($result[0]) && $result[0] =~ /abort: unknown revision/ ) {
        $options =~ s/-r \w+//;
        @result = execute_hg_command(1, 'clone', $options, $source, $dest);
    }
    return @result;
}

sub hg_parent
{
    my $repository  = shift;
    my $rev_id = shift;
    my $options = shift;

    if ( $debug ) {
        print STDERR "CWS-DEBUG: ... hg parent: 'repository', revision: '$rev_id', options: $options\n";
    }

    my @result = execute_hg_command(0, 'parent', "--cwd $repository", "-r $rev_id", $options);
    my $line = $result[0];
    chomp($line);
    return $line;
}

sub hg_pull
{
    my $repository  = shift;
    my $remote = shift;

    if ( $debug ) {
        print STDERR "CWS-DEBUG: ... hg pull: 'repository', remote: '$remote'\n";
    }

    my @result = execute_hg_command(0, 'pull', "--cwd $repository", $remote);
    my $line = $result[0];
    if ($line =~ /abort: /) {
        return undef;
    }
}

sub hg_update
{
    my $repository  = shift;

    if ( $debug ) {
        print STDERR "CWS-DEBUG: ... hg update: 'repository'\n";
    }

    my @result = execute_hg_command(1, 'update', "--cwd $repository");
    return @result;
}

sub hg_show
{
    if ( $debug ) {
        print STDERR "CWS-DEBUG: ... hg show\n";
    }
    my $result = execute_hg_command(0, 'show', '');
    return $result;
}

sub execute_hg_command
{
    my $terminate_on_rc = shift;
    my $command = shift;
    my $options = shift;
    my @args = @_;

    my $args_str = join(" ", @args);
    
    # we can only parse english strings, hopefully a C locale is available everywhere
    $ENV{LC_ALL}='C';
    $command = "hg $command $options $args_str";

    if ( $debug ) {
        print STDERR "CWS-DEBUG: ... execute command line: '$command'\n";
    }

    my @result;
    open(OUTPUT, "$command 2>&1 |") or print_error("Can't execute mercurial command line client", 98);
    while (<OUTPUT>) {
        push(@result, $_);
    }
    close(OUTPUT);
    
    my $rc = $? >> 8;

    if ( $rc > 0 && $terminate_on_rc) {
        print STDERR @result;
        print_error("The mercurial command line client failed with exit status '$rc'", 99);
    }
    return wantarray ? @result : \@result;
}


# vim: set ts=4 shiftwidth=4 expandtab syntax=perl:
