1# ====================================================================
2#    Licensed to the Apache Software Foundation (ASF) under one
3#    or more contributor license agreements.  See the NOTICE file
4#    distributed with this work for additional information
5#    regarding copyright ownership.  The ASF licenses this file
6#    to you under the Apache License, Version 2.0 (the
7#    "License"); you may not use this file except in compliance
8#    with the License.  You may obtain a copy of the License at
9#
10#      http://www.apache.org/licenses/LICENSE-2.0
11#
12#    Unless required by applicable law or agreed to in writing,
13#    software distributed under the License is distributed on an
14#    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15#    KIND, either express or implied.  See the License for the
16#    specific language governing permissions and limitations
17#    under the License.
18# ====================================================================
19
20require "English"
21require 'uri'
22require "svn/error"
23require "svn/util"
24require "svn/core"
25require "svn/wc"
26require "svn/ra"
27require "svn/ext/client"
28
29module Svn
30  module Client
31    Util.set_constants(Ext::Client, self)
32    Util.set_methods(Ext::Client, self)
33
34    class CommitItem
35      class << self
36        undef new
37      end
38    end
39
40    class CommitItem2
41      class << self
42        undef new
43      end
44    end
45
46    class CommitItem3
47      alias_method :wcprop_changes, :incoming_prop_changes
48      alias_method :wcprop_changes=, :incoming_prop_changes=
49    end
50
51    class CommitItemWrapper
52      def initialize(item)
53        @item = item
54      end
55
56      def incoming_prop_changes
57        if @item.incoming_prop_changes
58          Util.hash_to_prop_array(@item.incoming_prop_changes)
59        else
60          nil
61        end
62      end
63      alias_method :wcprop_changes, :incoming_prop_changes
64
65      def method_missing(method, *args, &block)
66        @item.__send__(method, *args, &block)
67      end
68    end
69
70    class Info
71      alias url URL
72      alias repos_root_url repos_root_URL
73
74      alias _last_changed_date last_changed_date
75      def last_changed_date
76        Time.from_apr_time(_last_changed_date)
77      end
78    end
79
80
81    # For backward compatibility
82    class PropListItem
83      # Returns an URI for the item concerned with the instance.
84      attr_accessor :name
85
86      # Returns a Hash of properties, such as
87      # <tt>{propname1 => propval1, propname2 => propval2, ...}</tt>.
88      attr_accessor :props
89
90      alias_method :node_name, :name
91      alias_method :prop_hash, :props
92
93      def initialize(name, props)
94        @name = name
95        @props = props
96      end
97
98      def method_missing(meth, *args)
99        if @props.respond_to?(meth)
100          @props.__send__(meth, *args)
101        else
102          super
103        end
104      end
105    end
106
107    Context = Ctx
108    class Context
109      alias _auth_baton auth_baton
110      alias _auth_baton= auth_baton=
111      remove_method :auth_baton, :auth_baton=
112      private :_auth_baton, :_auth_baton=
113
114      include Core::Authenticatable
115
116      alias _initialize initialize
117      private :_initialize
118      def initialize
119        _initialize
120        self.auth_baton = Core::AuthBaton.new
121        init_callbacks
122        return unless block_given?
123        begin
124          yield(self)
125        ensure
126          destroy
127        end
128      end
129
130      def destroy
131        Svn::Destroyer.destroy(self)
132      end
133
134      def auth_baton=(baton)
135        super(baton)
136        self._auth_baton = auth_baton
137      end
138
139      def checkout(url, path, revision=nil, peg_rev=nil,
140                   depth=nil, ignore_externals=false,
141                   allow_unver_obstruction=false)
142        revision ||= "HEAD"
143        Client.checkout3(url, path, peg_rev, revision, depth,
144                         ignore_externals, allow_unver_obstruction,
145                         self)
146      end
147      alias co checkout
148
149      def mkdir(*paths)
150        paths = paths.first if paths.size == 1 and paths.first.is_a?(Array)
151        mkdir2(:paths => paths)
152      end
153
154      MKDIR_REQUIRED_ARGUMENTS_KEYS = [:paths]
155      def mkdir2(arguments)
156        optional_arguments_defaults = {
157          :make_parents => false,
158          :revprop_table => {},
159        }
160
161        arguments = optional_arguments_defaults.merge(arguments)
162        Util.validate_options(arguments,
163                              optional_arguments_defaults.keys,
164                              MKDIR_REQUIRED_ARGUMENTS_KEYS)
165        Client.mkdir3(normalize_path(arguments[:paths]),
166                      arguments[:make_parents],
167                      arguments[:revprop_table],
168                      self)
169      end
170
171      def mkdir_p(*paths)
172        revprop_table = paths.pop if paths.last.is_a?(Hash)
173        paths = paths.first if paths.size == 1 and paths.first.is_a?(Array)
174        mkdir_p2(:paths => paths, :revprop_table => revprop_table || {})
175      end
176
177      def mkdir_p2(arguments)
178        mkdir2(arguments.update(:make_parents => true))
179      end
180
181      def commit(targets, recurse=true, keep_locks=false,
182                 keep_changelist=false, changelist_name=nil,
183                 revprop_table=nil)
184        targets = [targets] unless targets.is_a?(Array)
185        Client.commit4(targets, recurse, keep_locks, keep_changelist,
186                       changelist_name, revprop_table, self)
187      end
188      alias ci commit
189
190      def status(path, rev=nil, depth_or_recurse=nil, get_all=false,
191                 update=true, no_ignore=false,
192                 ignore_externals=false, changelists_names=nil, &status_func)
193        depth = Core::Depth.infinity_or_immediates_from_recurse(depth_or_recurse)
194        changelists_names = [changelists_names] unless changelists_names.is_a?(Array) or changelists_names.nil?
195        Client.status3(path, rev, status_func,
196                       depth, get_all, update, no_ignore,
197                       ignore_externals, changelists_names, self)
198      end
199      alias st status
200
201      def add(path, recurse=true, force=false, no_ignore=false)
202        Client.add3(path, recurse, force, no_ignore, self)
203      end
204
205      def delete(paths, force=false, keep_local=false, revprop_table=nil)
206        paths = [paths] unless paths.is_a?(Array)
207        Client.delete3(paths, force, keep_local, revprop_table, self)
208      end
209      alias del delete
210      alias remove delete
211      alias rm remove
212
213      def rm_f(*paths)
214        paths = paths.first if paths.size == 1 and paths.first.is_a?(Array)
215        rm(paths, true)
216      end
217
218      def update(paths, rev="HEAD", depth=nil, ignore_externals=false,
219                 allow_unver_obstruction=false, depth_is_sticky=false)
220        paths_is_array = paths.is_a?(Array)
221        paths = [paths] unless paths_is_array
222        result = Client.update3(paths, rev, depth, depth_is_sticky,
223                                ignore_externals, allow_unver_obstruction,
224                                self)
225        result = result.first unless paths_is_array
226        result
227      end
228      alias up update
229
230      def import(path, uri, depth_or_recurse=true, no_ignore=false, revprop_table=nil)
231        depth = Core::Depth.infinity_or_immediates_from_recurse(depth_or_recurse)
232        Client.import3(path, uri, depth, no_ignore, false, revprop_table, self)
233      end
234
235      def cleanup(dir)
236        Client.cleanup(dir, self)
237      end
238
239      def relocate(dir, from, to, recurse=true)
240        Client.relocate(dir, from, to, recurse, self)
241      end
242
243      def revert(paths, recurse=true)
244        paths = [paths] unless paths.is_a?(Array)
245        Client.revert(paths, recurse, self)
246      end
247
248      def resolved(path, recurse=true)
249        Client.resolved(path, recurse, self)
250      end
251
252      RESOLVE_REQUIRED_ARGUMENTS_KEYS = [:path]
253      def resolve(arguments={})
254        arguments = arguments.reject {|k, v| v.nil?}
255        optional_arguments_defaults = {
256          :depth => nil,
257          :conflict_choice => Wc::CONFLICT_CHOOSE_POSTPONE
258        }
259        arguments = optional_arguments_defaults.merge(arguments)
260        Util.validate_options(arguments,
261                              optional_arguments_defaults.keys,
262                              RESOLVE_REQUIRED_ARGUMENTS_KEYS)
263
264        Client.resolve(arguments[:path], arguments[:depth], arguments[:conflict_choice], self)
265      end
266
267      def propset(name, value, target, depth_or_recurse=nil, force=false,
268                  base_revision_for_url=nil, changelists_names=nil,
269                  revprop_table=nil)
270        base_revision_for_url ||= Svn::Core::INVALID_REVNUM
271        depth = Core::Depth.infinity_or_empty_from_recurse(depth_or_recurse)
272        changelists_names = [changelists_names] unless changelists_names.is_a?(Array) or changelists_names.nil?
273        Client.propset3(name, value, target, depth, force,
274                        base_revision_for_url, changelists_names,
275                        revprop_table, self)
276      end
277      alias prop_set propset
278      alias pset propset
279      alias ps propset
280
281      def propdel(name, *args)
282        propset(name, nil, *args)
283      end
284      alias prop_del propdel
285      alias pdel propdel
286      alias pd propdel
287
288      # Returns a value of a property, with +name+ attached to +target+,
289      # as a Hash such as <tt>{uri1 => value1, uri2 => value2, ...}</tt>.
290      def propget(name, target, rev=nil, peg_rev=nil, depth_or_recurse=nil,
291                  changelists_names=nil)
292        rev ||= "HEAD"
293        peg_rev ||= rev
294        depth = Core::Depth.infinity_or_empty_from_recurse(depth_or_recurse)
295        changelists_names = [changelists_names] unless changelists_names.is_a?(Array) or changelists_names.nil?
296        Client.propget3(name, target, peg_rev, rev, depth, changelists_names, self).first
297      end
298      alias prop_get propget
299      alias pget propget
300      alias pg propget
301
302      # Obsoleted document.
303      #
304      # Returns list of properties attached to +target+ as an Array of
305      # Svn::Client::PropListItem.
306      # Paths and URIs are available as +target+.
307      def proplist(target, peg_rev=nil, rev=nil, depth_or_recurse=nil,
308                   changelists_names=nil, &block)
309        rev ||= "HEAD"
310        peg_rev ||= rev
311        items = []
312        depth = Core::Depth.infinity_or_empty_from_recurse(depth_or_recurse)
313        receiver = Proc.new do |path, prop_hash|
314          items << PropListItem.new(path, prop_hash)
315          block.call(path, prop_hash) if block
316        end
317        changelists_names = [changelists_names] unless changelists_names.is_a?(Array) or changelists_names.nil?
318        Client.proplist3(target, peg_rev, rev, depth, changelists_names,
319                         receiver, self)
320        items
321      end
322      alias prop_list proplist
323      alias plist proplist
324      alias pl proplist
325
326      def copy(src_paths, dst_path, rev_or_copy_as_child=nil,
327               make_parents=nil, revprop_table=nil)
328        if src_paths.is_a?(Array)
329          copy_as_child = rev_or_copy_as_child
330          if copy_as_child.nil?
331            copy_as_child = src_paths.size == 1 ? false : true
332          end
333          src_paths = src_paths.collect do |path|
334            if path.is_a?(CopySource)
335              path
336            else
337              CopySource.new(path)
338            end
339          end
340        else
341          copy_as_child = false
342          unless src_paths.is_a?(CopySource)
343            src_paths = CopySource.new(src_paths, rev_or_copy_as_child)
344          end
345          src_paths = [src_paths]
346        end
347        Client.copy4(src_paths, dst_path, copy_as_child, make_parents,
348                     revprop_table, self)
349      end
350      alias cp copy
351
352      def move(src_paths, dst_path, force=false, move_as_child=nil,
353               make_parents=nil, revprop_table=nil)
354        src_paths = [src_paths] unless src_paths.is_a?(Array)
355        move_as_child = src_paths.size == 1 ? false : true if move_as_child.nil?
356        Client.move5(src_paths, dst_path, force, move_as_child, make_parents,
357                     revprop_table, self)
358      end
359      alias mv move
360
361      def mv_f(src_paths, dst_path, move_as_child=nil)
362        move(src_paths, dst_path, true, move_as_child)
363      end
364
365      def diff(options, path1, rev1, path2, rev2,
366               out_file, err_file, depth=nil,
367               ignore_ancestry=false,
368               no_diff_deleted=false, force=false,
369               header_encoding=nil, relative_to_dir=nil, changelists=nil)
370        header_encoding ||= Core::LOCALE_CHARSET
371        relative_to_dir &&= Core.path_canonicalize(relative_to_dir)
372        Client.diff4(options, path1, rev1, path2, rev2, relative_to_dir,
373                     depth, ignore_ancestry,
374                     no_diff_deleted, force, header_encoding,
375                     out_file, err_file, changelists, self)
376      end
377
378      def diff_peg(options, path, start_rev, end_rev,
379                   out_file, err_file, peg_rev=nil,
380                   depth=nil, ignore_ancestry=false,
381                   no_diff_deleted=false, force=false,
382                   header_encoding=nil, relative_to_dir=nil, changelists=nil)
383        header_encoding ||= Core::LOCALE_CHARSET
384        relative_to_dir &&= Core.path_canonicalize(relative_to_dir)
385        Client.diff_peg4(options, path, peg_rev, start_rev, end_rev,
386                         relative_to_dir, depth, ignore_ancestry,
387                         no_diff_deleted, force, header_encoding,
388                         out_file, err_file, changelists, self)
389      end
390
391      # Invokes block once for each item changed between <tt>path1</tt>
392      # at <tt>rev1</tt> and <tt>path2</tt> at <tt>rev2</tt>,
393      # and returns +nil+.
394      # +diff+ is an instance of Svn::Client::DiffSummarize.
395      def diff_summarize(path1, rev1, path2, rev2,
396                         depth=nil, ignore_ancestry=true, changelists=nil,
397                         &block) # :yields: diff
398        Client.diff_summarize2(path1, rev1, path2, rev2,
399                               depth, ignore_ancestry, changelists, block,
400                               self)
401      end
402
403      def diff_summarize_peg(path1, rev1, rev2, peg_rev=nil,
404                             depth=nil, ignore_ancestry=true, changelists=nil,
405                             &block)
406        Client.diff_summarize_peg2(path1, rev1, rev2, peg_rev,
407                                   depth, ignore_ancestry, changelists, block,
408                                   self)
409      end
410
411      def merge(src1, rev1, src2, rev2, target_wcpath,
412                depth=nil, ignore_ancestry=false,
413                force=false, dry_run=false, options=nil, record_only=false)
414        Client.merge3(src1, rev1, src2, rev2, target_wcpath,
415                      depth, ignore_ancestry, force, record_only,
416                      dry_run, options, self)
417      end
418
419
420      def merge_peg(src, rev1, rev2, *rest)
421        merge_peg2(src, [[rev1, rev2]], *rest)
422      end
423
424      def merge_peg2(src, ranges_to_merge, target_wcpath,
425                     peg_rev=nil, depth=nil,
426                     ignore_ancestry=false, force=false,
427                     dry_run=false, options=nil, record_only=false)
428        peg_rev ||= URI(src).scheme ? 'HEAD' : 'WORKING'
429        Client.merge_peg3(src, ranges_to_merge, peg_rev,
430                          target_wcpath, depth, ignore_ancestry,
431                          force, record_only, dry_run, options, self)
432      end
433
434      # Returns a content of +path+ at +rev+ as a String.
435      def cat(path, rev="HEAD", peg_rev=nil, output=nil)
436        used_string_io = output.nil?
437        output ||= StringIO.new
438        Client.cat2(output, path, peg_rev, rev, self)
439        if used_string_io
440          output.rewind
441          output.read
442        else
443          output
444        end
445      end
446
447      def lock(targets, comment=nil, steal_lock=false)
448        targets = [targets] unless targets.is_a?(Array)
449        Client.lock(targets, comment, steal_lock, self)
450      end
451
452      def unlock(targets, break_lock=false)
453        targets = [targets] unless targets.is_a?(Array)
454        Client.unlock(targets, break_lock, self)
455      end
456
457      def info(path_or_uri, rev=nil, peg_rev=nil, depth_or_recurse=false,
458               changelists=nil)
459        rev ||= URI(path_or_uri).scheme ? "HEAD" : "BASE"
460        depth = Core::Depth.infinity_or_empty_from_recurse(depth_or_recurse)
461        peg_rev ||= rev
462        receiver = Proc.new do |path, info|
463          yield(path, info)
464        end
465        Client.info2(path_or_uri, rev, peg_rev, receiver, depth, changelists,
466                     self)
467      end
468
469      # Returns URL for +path+ as a String.
470      def url_from_path(path)
471        Client.url_from_path(path)
472      end
473
474      def uuid_from_path(path, adm)
475        Client.uuid_from_path(path, adm, self)
476      end
477
478      # Returns UUID for +url+ as a String.
479      def uuid_from_url(url)
480        Client.uuid_from_url(url, self)
481      end
482
483      def open_ra_session(url)
484        Client.open_ra_session(url, self)
485      end
486
487      # Scans revisions from +start_rev+ to +end_rev+ for each path in
488      # +paths+, invokes block once for each revision, and then returns
489      # +nil+.
490      #
491      # When +discover_changed_paths+ is +false+ or +nil+, +changed_paths+,
492      # the first block-argument, is +nil+.  Otherwise, it is a Hash
493      # containing simple information associated with the revision,
494      # whose keys are paths and values are changes, such as
495      # <tt>{path1 => change1, path2 => change2, ...}</tt>,
496      # where each path is an absolute one in the repository and each
497      # change is a instance of Svn::Core::LogChangedPath.
498      # The rest of the block arguments, +rev+, +author+, +date+, and
499      # +message+ are the revision number, author, date, and the log
500      # message of that revision, respectively.
501      def log(paths, start_rev, end_rev, limit,
502              discover_changed_paths, strict_node_history,
503              peg_rev=nil)
504        paths = [paths] unless paths.is_a?(Array)
505        receiver = Proc.new do |changed_paths, rev, author, date, message|
506          yield(changed_paths, rev, author, date, message)
507        end
508        Client.log3(paths, peg_rev, start_rev, end_rev, limit,
509                    discover_changed_paths,
510                    strict_node_history,
511                    receiver, self)
512      end
513
514      # Returns log messages, for commits affecting +paths+ from +start_rev+
515      # to +end_rev+, as an Array of String.
516      # You can use URIs as well as paths as +paths+.
517      def log_message(paths, start_rev=nil, end_rev=nil)
518        start_rev ||= "HEAD"
519        end_rev ||= start_rev
520        messages = []
521        receiver = Proc.new do |changed_paths, rev, author, date, message|
522          messages << message
523        end
524        log(paths, start_rev, end_rev, 0, false, false) do |*args|
525          receiver.call(*args)
526        end
527        if !paths.is_a?(Array) and messages.size == 1
528          messages.first
529        else
530          messages
531        end
532      end
533
534      def blame(path_or_uri, start_rev=nil, end_rev=nil, peg_rev=nil,
535                diff_options=nil, ignore_mime_type=false)
536        start_rev ||= 1
537        end_rev ||= URI(path_or_uri).scheme ? "HEAD" : "BASE"
538        peg_rev ||= end_rev
539        diff_options ||= Svn::Core::DiffFileOptions.new
540        receiver = Proc.new do |line_no, revision, author, date, line|
541          yield(line_no, revision, author, date, line)
542        end
543        Client.blame3(path_or_uri, peg_rev, start_rev,
544                      end_rev, diff_options, ignore_mime_type,
545                      receiver, self)
546      end
547      alias praise blame
548      alias annotate blame
549      alias ann annotate
550
551      # Returns a value of a revision property named +name+ for +uri+
552      # at +rev+, as a String.
553      # Both URLs and paths are available as +uri+.
554      def revprop(name, uri, rev)
555        value, = revprop_get(name, uri, rev)
556        value
557      end
558      alias rp revprop
559
560      # Returns a value of a revision property named +name+ for +uri+
561      # at +rev+, as an Array such as <tt>[value, rev]</tt>.
562      # Both URLs and paths are available as +uri+.
563      def revprop_get(name, uri, rev)
564        result = Client.revprop_get(name, uri, rev, self)
565        if result.is_a?(Array)
566          result
567        else
568          [nil, result]
569        end
570      end
571      alias rpget revprop_get
572      alias rpg revprop_get
573
574      # Sets +value+ as a revision property named +name+ for +uri+ at +rev+.
575      # Both URLs and paths are available as +uri+.
576      def revprop_set(name, value, uri, rev, force=false)
577        Client.revprop_set(name, value, uri, rev, force, self)
578      end
579      alias rpset revprop_set
580      alias rps revprop_set
581
582      # Deletes a revision property, named +name+, for +uri+ at +rev+.
583      # Both URLs and paths are available as +uri+.
584      def revprop_del(name, uri, rev, force=false)
585        Client.revprop_set(name, nil, uri, rev, force, self)
586      end
587      alias rpdel revprop_del
588      alias rpd revprop_del
589
590      # Returns a list of revision properties set for +uri+ at +rev+,
591      # as an Array such as
592      # <tt>[{revprop1 => value1, revprop2 => value2, ...}, rev]</tt>.
593      # Both URLs and paths are available as +uri+.
594      def revprop_list(uri, rev)
595        props, rev = Client.revprop_list(uri, rev, self)
596        if props.has_key?(Svn::Core::PROP_REVISION_DATE)
597          props[Svn::Core::PROP_REVISION_DATE] =
598            Time.from_svn_format(props[Svn::Core::PROP_REVISION_DATE])
599        end
600        [props, rev]
601      end
602      alias rplist revprop_list
603      alias rpl revprop_list
604
605      def export(from, to, rev=nil, peg_rev=nil,
606                 force=false, ignore_externals=false,
607                 depth=nil, native_eol=nil)
608        Client.export4(from, to, rev, peg_rev, force,
609                       ignore_externals, depth, native_eol, self)
610      end
611
612      def ls(path_or_uri, rev=nil, peg_rev=nil, recurse=false)
613        rev ||= URI(path_or_uri).scheme ? "HEAD" : "BASE"
614        peg_rev ||= rev
615        Client.ls3(path_or_uri, rev, peg_rev, recurse, self)
616      end
617
618      # Invokes block once for each path below +path_or_uri+ at +rev+
619      # and returns +nil+.
620      # +path+ is a relative path from the +path_or_uri+.
621      # +dirent+ is an instance of Svn::Core::Dirent.
622      # +abs_path+ is an absolute path for +path_or_uri+ in the repository.
623      def list(path_or_uri, rev, peg_rev=nil, depth_or_recurse=Core::DEPTH_IMMEDIATES,
624               dirent_fields=nil, fetch_locks=true,
625               &block) # :yields: path, dirent, lock, abs_path
626        depth = Core::Depth.infinity_or_immediates_from_recurse(depth_or_recurse)
627        dirent_fields ||= Core::DIRENT_ALL
628        Client.list2(path_or_uri, peg_rev, rev, depth, dirent_fields,
629                    fetch_locks, block, self)
630      end
631
632      def switch(path, uri, peg_rev=nil, rev=nil, depth=nil,
633                 ignore_externals=false, allow_unver_obstruction=false,
634                 depth_is_sticky=false)
635
636        Client.switch2(path, uri, peg_rev, rev, depth, depth_is_sticky,
637                       ignore_externals, allow_unver_obstruction, self)
638      end
639
640      def set_log_msg_func(&callback)
641        callback_wrapper = Proc.new do |items|
642          items = items.collect do |item|
643            item_wrapper = CommitItemWrapper.new(item)
644          end
645          callback.call(items)
646        end
647        set_log_msg_func2(&callback_wrapper)
648      end
649
650      def set_log_msg_func2(&callback)
651        @log_msg_baton = Client.set_log_msg_func3(self, callback)
652      end
653
654      def set_notify_func(&callback)
655        @notify_baton = Client.set_notify_func2(self, callback)
656      end
657
658      def set_cancel_func(&callback)
659        @cancel_baton = Client.set_cancel_func(self, callback)
660      end
661
662      def config=(new_config)
663        Client.set_config(self, new_config)
664      end
665
666      def config
667        Client.get_config(self)
668      end
669
670      def merged(path_or_url, peg_revision=nil)
671        info = Client.mergeinfo_get_merged(path_or_url, peg_revision, self)
672        return nil if info.nil?
673        Core::MergeInfo.new(info)
674      end
675
676      def log_merged(path_or_url, peg_revision, merge_source_url,
677                     source_peg_revision, discover_changed_path=true,
678                     interested_revision_prop_names=nil,
679                     &receiver)
680        raise ArgumentError, "Block isn't given" if receiver.nil?
681        Client.mergeinfo_log_merged(path_or_url, peg_revision,
682                                    merge_source_url, source_peg_revision,
683                                    receiver, discover_changed_path,
684                                    interested_revision_prop_names,
685                                    self)
686      end
687
688      def add_to_changelist(changelist_name, paths, depth=nil, changelists_names=nil)
689        paths = [paths] unless paths.is_a?(Array)
690        changelists_names = [changelists_names] unless changelists_names.is_a?(Array) or changelists_names.nil?
691        Client.add_to_changelist(paths, changelist_name, depth, changelists_names, self)
692      end
693
694      def changelists(changelists_names, root_path, depth=nil, &block)
695        lists_contents = Hash.new{|h,k| h[k]=[]}
696        changelists_names = [changelists_names] unless changelists_names.is_a?(Array) or changelists_names.nil?
697        block ||= lambda{|path, changelist| lists_contents[changelist] << path }
698        Client.get_changelists(root_path, changelists_names, depth, block, self)
699        lists_contents
700      end
701
702      def remove_from_changelists(changelists_names, paths, depth=nil)
703        changelists_names = [changelists_names] unless changelists_names.is_a?(Array) or changelists_names.nil?
704        paths = [paths] unless paths.is_a?(Array)
705        Client.remove_from_changelists(paths, depth, changelists_names, self)
706      end
707
708      private
709      def init_callbacks
710        set_log_msg_func
711        set_notify_func
712        set_cancel_func
713      end
714      %w(log_msg notify cancel).each do |type|
715        private "#{type}_func", "#{type}_baton"
716        private "#{type}_func=", "#{type}_baton="
717      end
718      %w(notify).each do |type|
719        private "#{type}_func2", "#{type}_baton2"
720        private "#{type}_func2=", "#{type}_baton2="
721      end
722
723      def normalize_path(paths)
724        paths = [paths] unless paths.is_a?(Array)
725        paths.collect do |path|
726          path.chomp(File::SEPARATOR)
727        end
728      end
729    end
730
731    # Following methods are also available:
732    #
733    # [path]
734    #   Returns a path concerned with the instance.
735    # [prop_changed?]
736    #   Returns +true+ when the instance is a change involving a property
737    #   change.
738    class DiffSummarize
739      alias prop_changed? prop_changed
740
741      # Returns +true+ when the instance is a normal change.
742      def kind_normal?
743        summarize_kind == DIFF_SUMMARIZE_KIND_NORMAL
744      end
745
746      # Returns +true+ when the instance is a change involving addition.
747      def kind_added?
748        summarize_kind == DIFF_SUMMARIZE_KIND_ADDED
749      end
750
751      # Returns +true+ when the instance is a change involving modification.
752      def kind_modified?
753        summarize_kind == DIFF_SUMMARIZE_KIND_MODIFIED
754      end
755
756      # Returns +true+ when the instance is a change involving deletion.
757      def kind_deleted?
758        summarize_kind == DIFF_SUMMARIZE_KIND_DELETED
759      end
760
761      # Returns +true+ when the instance is a change made to no node.
762      def node_kind_none?
763        node_kind == Core::NODE_NONE
764      end
765
766      # Returns +true+ when the instance is a change made to a file node.
767      def node_kind_file?
768        node_kind == Core::NODE_FILE
769      end
770
771      # Returns +true+ when the instance is a change made to a directory node.
772      def node_kind_dir?
773        node_kind == Core::NODE_DIR
774      end
775
776      # Returns +true+ when the instance is a change made to an unknown node.
777      def node_kind_unknown?
778        node_kind == Core::NODE_UNKNOWN
779      end
780    end
781
782    class CopySource
783      alias_method :_initialize, :initialize
784      private :_initialize
785      def initialize(path, rev=nil, peg_rev=nil)
786        _initialize(path, rev, peg_rev)
787      end
788    end
789  end
790end
791