#######################################################################
#                  Licensing Information                              #
#                                                                     #
#  The following code is a derivative work of the code from the       #
#  Mercurial project, which is licensed GPLv2. This code therefore    #
#  is also licensed under the terms of the GNU Public License,        #
#  verison 2.                                                         #
#                                                                     #
#  For information on the license of this code when distributed       #
#  with and used in conjunction with the other modules in the         #
#  Amp project, please see the root-level LICENSE file.               #
#                                                                     #
#  © Michael J. Edgar and Ari Brown, 2009-2010                        #
#                                                                     #
#######################################################################

command :bundle do |c|
  c.workflow :hg
  
  c.desc "Create a changegroup file"
  c.help <<-EOS
amp bundle [options]+ file dest

  Generate a compressed changegroup file collecting changesets not
  known to be in another repository.
  
  If no destination repository is specified the destination is
  assumed to have all the nodes specified by one or more --base
  parameters. To create a bundle containing all changesets, use
  --all (or --base null). To change the compression method applied,
  use the -t option (by default, bundles are compressed using bz2).
  
  The bundle file can then be transferred using conventional means and
  applied to another repository with the unbundle or pull command.
  This is useful when direct push and pull are not available or when
  exporting an entire repository is undesirable.
  
  Applying bundles preserves all changeset contents including
  permissions, copy/rename information, and revision history.
  
  Options are:
EOS

  c.opt :force, "Run even when remote repository is unrelated",     :short => '-f'
  c.opt :rev,   "A changeset up to which you would like to bundle", :short => '-r', :type => :string
  c.opt :base,  "A base changeset to specify instead of a destination",             :type => :string, :multi => true
  c.opt :all,   "Bundle all changesets in the repository",          :short => '-a'
  c.opt :type,  "Bundle compression type to use (default: bzip2)",  :short => '-t',                   :default => 'bzip2'
  c.opt :ssh,   "Specify ssh command to use",                       :short => '-e', :type => :string
  c.opt :"remote-cmd", "Specify hg command to run on the remote side",              :type => :string
  
  c.on_run do |opts, args|
    repo  = opts[:repository]
    rev   = opts[:rev]
    fname = args.shift
    dest  = args.shift
    
    # Type notation!
    # rev :: Amp::Changeset
    rev  &&= repo.lookup rev
    base   = opts[:all] ? [nil] : (opts[:base] || []) # --all overrides --base
    
    # we want [nil] and ["1", "23"] to be triggered here.
    if !base.empty?
      if dest
        raise abort("--base is incompatible with specifiying a destination")
      end
      
      base = base.map {|b| repo.lookup b }
      
      o   = []
      has = {Amp::Mercurial::RevlogSupport::Node::NULL_ID => nil}
      
      # iterates over each file node id
      base.each do |filename, node_id|
        has.update repo.changelog.reachable_nodes_for_node(node_id)
      end
      
      visit = rev ? [rev] : repo.changelog.heads
      seen  = {} # {node => Boolean} where node is a string
      add   = proc do |node|
        seen[node] = true
        visit << node
      end
      
      until visit.empty?
        n = visit.shift
        
        # for those who are lame:
        # rents = 'rents = parents
        rents = repo.changelog.parents(n).select do |parental_unit|
          !has.include?(parental_unit)
        end
        
        if rents.empty?
          o.unshift n
        else
          rents.each {|rent| add[ rent ] unless seen.include? rent }
        end # end if
      end # end until
    else
      path = c.expand_path dest || 'default-push', dest || 'default', repo.config
      dest, revs, checkout = *c.parse_url(path, [rev])
      # alio is Esperanto for "other"; it's conveniently the same length as repo
      alio = Amp::Repositories.pick nil, dest
      o    = repo.find_outgoing_roots alio, :force => opts[:force]
    end # end if
    
    # Oh no, bitches! If you thought we were done, you'd be wrong.
    # Nevermind, false alarm. Turns out there's not that much left to do.
    
    cg = if revs
           repo.changegroup_subset o, [rev], 'bundle'
         else
           p [">>", o]
           # !!!!!!!!!!!! The bug is in the following line !!!!!!!!! KILLME
           repo.changegroup o, 'bundle'
         end
    
    # these few lines convert nice human speak to icky computer speak
    bundle_type = opts[:type].downcase
    btypes      = {'none' => 'HG10UN', 'bzip2' => 'HG10BZ', 'gzip' => 'HG10GZ'}
    bundle_type = btypes[bundle_type]
    
    # some error checking
    # yes, this calls Array#include, but the array is only 3 elements
    unless Amp::Mercurial::RevlogSupport::ChangeGroup::BUNDLE_TYPES.include? bundle_type
      raise abort('unknown bundle type specified with --type')
    end
    
    File.open fname, 'w' do |file|
      Amp::Mercurial::RevlogSupport::ChangeGroup.write_bundle cg, bundle_type, file
    end
  end  # end on_run
end