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 "svn/error"
22require "svn/util"
23require "svn/core"
24require "svn/delta"
25require "svn/ext/wc"
26
27module Svn
28  module Wc
29    Util.set_constants(Ext::Wc, self)
30    Util.set_methods(Ext::Wc, self)
31    self.swig_init_asp_dot_net_hack()
32
33    @@alias_targets = %w(parse_externals_description
34                         ensure_adm cleanup)
35    class << self
36      @@alias_targets.each do |target|
37        alias_method "_#{target}", target
38      end
39    end
40    @@alias_targets.each do |target|
41      alias_method "_#{target}", target
42    end
43    @@alias_targets = nil
44
45    module_function
46    def locked?(path)
47      Wc.locked(path)
48    end
49
50    def ensure_adm(*args)
51      AdmAccess.ensure(*args)
52    end
53
54    # For backward compatibility
55    def parse_externals_description(*args)
56      ExternalsDescription.parse(*args)
57    end
58
59    def actual_target(path)
60      Wc.get_actual_target(path)
61    end
62
63    def normal_prop?(name)
64      Wc.is_normal_prop(name)
65    end
66
67    def wc_prop?(name)
68      Wc.is_wc_prop(name)
69    end
70
71    def entry_prop?(name)
72      Wc.is_entry_prop(name)
73    end
74
75    def pristine_copy_path(path)
76      Wc.get_pristine_copy_path(path)
77    end
78
79    def default_ignores(config)
80      Wc.get_default_ignores(config)
81    end
82
83    def cleanup(path, diff3_cmd=nil, cancel_func=nil)
84      Wc.cleanup2(path, diff3_cmd, cancel_func)
85    end
86
87    def ignore?(path, patterns)
88      Wc.match_ignore_list(path, patterns)
89    end
90
91    module ExternalsDescription
92      module_function
93      def parse(parent_dir, desc, canonicalize_url=true)
94        Wc.parse_externals_description3(parent_dir, desc, canonicalize_url)
95      end
96    end
97
98    class ExternalItem
99      class << self
100        undef new
101      end
102    end
103
104    AdmAccess = SWIG::TYPE_p_svn_wc_adm_access_t
105    class AdmAccess
106      class << self
107        def ensure(path, uuid, url, repos, revision, depth=nil)
108          Wc.ensure_adm3(path, uuid, url, repos, revision, depth)
109        end
110
111        def open(associated, path, write_lock=true,
112                 depth=-1, cancel_func=nil, &block)
113          _open(:adm_open3, associated, path, write_lock,
114                depth, cancel_func, &block)
115        end
116
117        def probe_open(associated, path, write_lock=true, depth=-1,
118                       cancel_func=nil, &block)
119          _open(:adm_probe_open3, associated, path, write_lock,
120                depth, cancel_func, &block)
121        end
122
123        def open_anchor(path, write_lock=true, depth=-1,
124                        cancel_func=nil, &block)
125          _open(:adm_open_anchor, path, write_lock, depth,
126                cancel_func, &block)
127        end
128
129        private
130        def _open(name, *args, &block)
131          results = Wc.__send__(name, *args, &block)
132          adm, *rest = results
133
134          if block_given?
135            begin
136              yield *results
137            ensure
138              adm.close
139            end
140          else
141            results
142          end
143        end
144      end
145
146      attr_accessor :traversal_info
147
148      def open(*args, &block)
149        self.class.open(self, *args, &block)
150      end
151
152      def probe_open(*args, &block)
153        self.class.probe_open(self, *args, &block)
154      end
155
156      def retrieve(path)
157        Wc.adm_retrieve(self, path)
158      end
159
160      def probe_retrieve(path)
161        Wc.adm_probe_retrieve(self, path)
162      end
163
164      def probe_try(path, write_lock, depth, &cancel_func)
165        Wc.adm_probe_try3(self, path, write_lock, depth, cancel_func)
166      end
167
168      def close
169        Wc.adm_close(self)
170      end
171
172      def path
173        Wc.adm_access_path(self)
174      end
175
176      def locked?
177        Wc.adm_locked(self)
178      end
179
180      def has_binary_prop?(path)
181        Wc.has_binary_prop(path, self)
182      end
183
184      def text_modified?(filename, force=false)
185        Wc.text_modified_p(filename, force, self)
186      end
187
188      def props_modified?(path)
189        Wc.props_modified_p(path, self)
190      end
191
192      def entry(path, show_hidden=false)
193        Entry.new(path, self, show_hidden, Svn::Core::Pool.new)
194      end
195
196      def read_entries(show_hidden=false)
197        Wc.entries_read(self, show_hidden, Svn::Core::Pool.new)
198      end
199
200      def ancestry(path)
201        Wc.get_ancestry(path, self)
202      end
203
204      def walk_entries(path, callbacks, show_hidden=false, cancel_func=nil,
205                       depth=nil)
206        Wc.walk_entries3(path, self, callbacks, depth, show_hidden,
207                         cancel_func)
208      end
209
210      def mark_missing_deleted(path)
211        Wc.mark_missing_deleted(path, self)
212      end
213
214      def maybe_set_repos_root(path, repos)
215        Wc.maybe_set_repos_root(self, path, repos)
216      end
217
218      def status(path)
219        Wc.status2(path, self)
220      end
221
222      def status_editor(target, config, recurse=true,
223                        get_all=true, no_ignore=true,
224                        cancel_func=nil, traversal_info=nil)
225        traversal_info ||= _traversal_info
226        status_func = Proc.new do |path, status|
227          yield(path, status)
228        end
229        ret = Wc.get_status_editor2(self, target, config, recurse,
230                                    get_all, no_ignore, status_func,
231                                    cancel_func, traversal_info)
232        editor, editor_baton, set_lock_baton = *ret
233        editor.instance_variable_set("@__status_fun__", status_func)
234        editor.baton = editor_baton
235        def set_lock_baton.set_repos_locks(locks, repos_root)
236          Wc.status_set_repos_locks(self, locks, repos_root)
237        end
238        [editor, set_lock_baton]
239      end
240
241      def copy(src, dst_basename, cancel_func=nil, notify_func=nil)
242        Wc.copy2(src, self, dst_basename, cancel_func, notify_func)
243      end
244
245      def delete(path, cancel_func=nil, notify_func=nil, keep_local=false)
246        Wc.delete3(path, self, cancel_func, notify_func, keep_local)
247      end
248
249      def add(path, copyfrom_url=nil, copyfrom_rev=0,
250              cancel_func=nil, notify_func=nil)
251        Wc.add2(path, self, copyfrom_url, copyfrom_rev,
252                cancel_func, notify_func)
253      end
254
255      def add_repos_file(dst_path, new_text_path, new_props,
256                         copyfrom_url=nil, copyfrom_rev=0)
257        Wc.add_repos_file(dst_path, self, new_text_path,
258                          new_props, copyfrom_url, copyfrom_rev)
259      end
260
261      def add_repos_file2(dst_path, new_text_base_path, new_base_props,
262                          new_text_path=nil, new_props=nil,
263                          copyfrom_url=nil, copyfrom_rev=0)
264        Wc.add_repos_file2(dst_path, self,
265                           new_text_base_path, new_text_path,
266                           new_base_props, new_props,
267                           copyfrom_url, copyfrom_rev)
268      end
269
270      def remove_from_revision_control(name, destroy_wf=true,
271                                       instant_error=true,
272                                       cancel_func=nil)
273        Wc.remove_from_revision_control(self, name,
274                                        destroy_wf,
275                                        instant_error,
276                                        cancel_func)
277      end
278
279      def resolved_conflict(path, resolve_text=true,
280                            resolve_props=true, recurse=true,
281                            notify_func=nil, cancel_func=nil)
282        Wc.resolved_conflict2(path, self, resolve_text,
283                              resolve_props, recurse,
284                              notify_func, cancel_func)
285      end
286
287      def process_committed(path, new_revnum, rev_date=nil, rev_author=nil,
288                            wcprop_changes=[], recurse=true,
289                            remove_lock=true, digest=nil,
290                            remove_changelist=false)
291        Wc.process_committed4(path, self, recurse,
292                              new_revnum, rev_date,
293                              rev_author, wcprop_changes,
294                              remove_lock, remove_changelist, digest)
295      end
296
297      def crawl_revisions(path, reporter, restore_files=true,
298                          depth=nil, use_commit_times=true,
299                          notify_func=nil, traversal_info=nil)
300        traversal_info ||= _traversal_info
301        Wc.crawl_revisions3(path, self, reporter, reporter.baton,
302                            restore_files, depth, use_commit_times,
303                            notify_func, traversal_info)
304      end
305
306      def wc_root?(path)
307        Wc.is_wc_root(path, self)
308      end
309
310      def update_editor(target, target_revision=nil, use_commit_times=nil,
311                        depth=nil, allow_unver_obstruction=nil, diff3_cmd=nil,
312                        notify_func=nil, cancel_func=nil, traversal_info=nil,
313                        preserved_exts=nil)
314        update_editor2(:target => target,
315                       :target_revision => target_revision,
316                       :use_commit_times => use_commit_times,
317                       :depth => depth,
318                       :allow_unver_obstruction => allow_unver_obstruction,
319                       :diff3_cmd => diff3_cmd,
320                       :notify_func => notify_func,
321                       :cancel_func => cancel_func,
322                       :traversal_info => traversal_info,
323                       :preserved_exts => preserved_exts )
324      end
325
326      UPDATE_EDITOR2_REQUIRED_ARGUMENTS_KEYS = [:target]
327      def update_editor2(arguments={})
328        arguments = arguments.reject {|k, v| v.nil?}
329        optional_arguments_defaults = {
330            :target_revision => nil,
331            :use_commit_times => true,
332            :depth => nil,
333            :depth_is_sticky => false,
334            :allow_unver_obstruction => false,
335            :diff3_cmd => nil,
336            :notify_func => nil,
337            :cancel_func => nil,
338            :conflict_func => nil,
339            :traversal_info => _traversal_info,
340            :preserved_exts => []
341          }
342
343        arguments = optional_arguments_defaults.merge(arguments)
344        Util.validate_options(arguments,
345                              optional_arguments_defaults.keys,
346                              UPDATE_EDITOR2_REQUIRED_ARGUMENTS_KEYS)
347
348        # TODO(rb support fetch_fun): implement support for the fetch_func
349        # callback.
350        arguments[:fetch_func] = nil
351
352        results = Wc.get_update_editor3(arguments[:target_revision], self,
353                                        arguments[:target],
354                                        arguments[:use_commit_times],
355                                        arguments[:depth],
356                                        arguments[:depth_is_sticky],
357                                        arguments[:allow_unver_obstruction],
358                                        arguments[:notify_func],
359                                        arguments[:cancel_func],
360                                        arguments[:conflict_func],
361                                        arguments[:fetch_func],
362                                        arguments[:diff3_cmd],
363                                        arguments[:preserved_exts],
364                                        arguments[:traversal_info])
365        target_revision_address, editor, editor_baton = results
366        editor.__send__(:target_revision_address=, target_revision_address)
367        editor.baton = editor_baton
368        editor
369      end
370
371      def switch_editor(target, switch_url, target_revision=nil,
372                        use_commit_times=nil, depth=nil,
373                        allow_unver_obstruction=nil, diff3_cmd=nil,
374                        notify_func=nil, cancel_func=nil, traversal_info=nil,
375                        preserved_exts=nil)
376        switch_editor2(:target => target,
377                       :switch_url => switch_url,
378                       :target_revision => target_revision,
379                       :use_commit_times => use_commit_times,
380                       :depth => depth,
381                       :allow_unver_obstruction => allow_unver_obstruction,
382                       :diff3_cmd => diff3_cmd,
383                       :notify_func => notify_func,
384                       :cancel_func => cancel_func,
385                       :traversal_info => traversal_info,
386                       :preserved_exts => preserved_exts )
387       end
388
389      SWITCH_EDITOR2_REQUIRED_ARGUMENTS_KEYS = [:target, :switch_url]
390      def switch_editor2(arguments={})
391        arguments = arguments.reject {|k, v| v.nil?}
392        optional_arguments_defaults = {
393          :target_revision => nil,
394          :use_commit_times => true,
395          :depth => nil,
396          :depth_is_sticky => false,
397          :allow_unver_obstruction => false,
398          :diff3_cmd => nil,
399          :notify_func => nil,
400          :cancel_func => nil,
401          :conflict_func => nil,
402          :traversal_info => _traversal_info,
403          :preserved_exts => []
404        }
405        arguments = optional_arguments_defaults.merge(arguments)
406        Util.validate_options(arguments,
407                              optional_arguments_defaults.keys,
408                              SWITCH_EDITOR2_REQUIRED_ARGUMENTS_KEYS)
409
410        results = Wc.get_switch_editor3(arguments[:target_revision], self,
411                                        arguments[:target],
412                                        arguments[:switch_url],
413                                        arguments[:use_commit_times],
414                                        arguments[:depth],
415                                        arguments[:depth_is_sticky],
416                                        arguments[:allow_unver_obstruction],
417                                        arguments[:notify_func],
418                                        arguments[:cancel_func],
419                                        arguments[:conflict_func],
420                                        arguments[:diff3_cmd],
421                                        arguments[:preserved_exts],
422                                        arguments[:traversal_info])
423        target_revision_address, editor, editor_baton = results
424        editor.__send__(:target_revision_address=, target_revision_address)
425        editor.baton = editor_baton
426        editor
427      end
428
429      def prop_list(path)
430        Wc.prop_list(path, self)
431      end
432
433      def prop(name, path)
434        Wc.prop_get(name, path, self)
435      end
436
437      def set_prop(name, value, path, skip_checks=false)
438        Wc.prop_set2(name, value, path, self, skip_checks)
439      end
440
441      def diff_editor(target, callbacks, depth=nil,
442                      ignore_ancestry=true, use_text_base=false,
443                      reverse_order=false, cancel_func=nil)
444        callbacks_wrapper = DiffCallbacksWrapper.new(callbacks)
445        args = [target, callbacks_wrapper, depth, ignore_ancestry,
446                use_text_base, reverse_order, cancel_func]
447        diff_editor2(*args)
448      end
449
450      def diff_editor2(target, callbacks, depth=nil,
451                       ignore_ancestry=true, use_text_base=false,
452                       reverse_order=false, cancel_func=nil, changelists=nil)
453        editor, editor_baton = Wc.get_diff_editor4(self, target, callbacks,
454                                                   depth, ignore_ancestry,
455                                                   use_text_base, reverse_order,
456                                                   cancel_func, changelists)
457        editor.baton = editor_baton
458        editor
459      end
460
461      def diff(target, callbacks, recurse=true, ignore_ancestry=true)
462        callbacks_wrapper = DiffCallbacksWrapper.new(callbacks)
463        args = [target, callbacks_wrapper, recurse, ignore_ancestry]
464        diff2(*args)
465      end
466
467      def diff2(target, callbacks, recurse=true, ignore_ancestry=true)
468        Wc.diff3(self, target, callbacks, recurse, ignore_ancestry)
469      end
470
471      def prop_diffs(path)
472        Wc.get_prop_diffs(path, self)
473      end
474
475      def merge(left, right, merge_target, left_label,
476                right_label, target_label, dry_run=false,
477                diff3_cmd=nil, merge_options=nil)
478        Wc.merge2(left, right, merge_target, self,
479                  left_label, right_label, target_label,
480                  dry_run, diff3_cmd, merge_options)
481      end
482
483      def merge_props(path, baseprops, propchanges, base_merge=true,
484                      dry_run=false)
485        Wc.merge_props(path, self, baseprops, propchanges,
486                      base_merge, dry_run)
487      end
488
489      def merge_prop_diffs(path, propchanges, base_merge=true,
490                           dry_run=false)
491        Wc.merge_prop_diffs(path, self, propchanges,
492                           base_merge, dry_run)
493      end
494
495      def relocate(path, from, to, recurse=true, old_validator=nil, &validator)
496        if validator.nil? and !old_validator.nil?
497          validator = Proc.new do |uuid, url, root_url|
498            old_validator.call(uuid,
499                               root_url ? root_url : url,
500                               root_url ? true : false)
501          end
502        end
503        Wc.relocate3(path, self, from, to, recurse, validator)
504      end
505
506      def revert(path, recurse=true, use_commit_times=true,
507                 cancel_func=nil, notify_func=nil)
508        Wc.revert2(path, self, recurse, use_commit_times,
509                   cancel_func, notify_func)
510      end
511
512      def translated_file(src, versioned_file, flags)
513        temp = Wc.translated_file2(src, versioned_file, self, flags)
514        temp.close
515        path = temp.path
516        path.instance_variable_set("@__temp__", temp)
517        path
518      end
519
520      def translated_file2(src, versioned_file, flags)
521        Wc.translated_file2(src, versioned_file, self, flags)
522      end
523
524      def translated_stream(path, versioned_file, flags)
525        Wc.translated_stream(path, versioned_file, self, flags)
526      end
527
528      def transmit_text_deltas(path, editor, file_baton, fulltext=false)
529        editor.baton = file_baton
530        Wc.transmit_text_deltas(path, self, fulltext, editor)
531      end
532
533      def transmit_text_deltas2(path, editor, fulltext=false)
534        Wc.transmit_text_deltas2(path, self, fulltext, editor)
535      end
536
537      def transmit_prop_deltas(path, entry, editor, baton=nil)
538        editor.baton = baton if baton
539        Wc.transmit_prop_deltas(path, self, entry, editor)
540      end
541
542      def ignores(config)
543        Wc.get_ignores(config, self)
544      end
545
546      def add_lock(path, lock)
547        Wc.add_lock(path, lock, self)
548      end
549
550      def remove_lock(path)
551        Wc.remove_lock(path, self)
552      end
553
554      def set_changelist(path, changelist_name, cancel_func=nil,
555                         notify_func=nil)
556        Wc.set_changelist(path, changelist_name, self, cancel_func,
557                          notify_func)
558      end
559
560      private
561      def _traversal_info
562        @traversal_info ||= nil
563      end
564    end
565
566    class DiffCallbacksWrapper
567      def initialize(original)
568        @original = original
569      end
570
571      def file_changed(access, path, tmpfile1, tmpfile2, rev1,
572                       rev2, mimetype1, mimetype2,
573                       prop_changes, original_props)
574        prop_changes = Util.hash_to_prop_array(prop_changes)
575        @original.file_changed(access, path, tmpfile1, tmpfile2, rev1,
576                               rev2, mimetype1, mimetype2,
577                               prop_changes, original_props)
578      end
579
580      def file_added(access, path, tmpfile1, tmpfile2, rev1,
581                     rev2, mimetype1, mimetype2,
582                     prop_changes, original_props)
583        prop_changes = Util.hash_to_prop_array(prop_changes)
584        @original.file_added(access, path, tmpfile1, tmpfile2, rev1,
585                             rev2, mimetype1, mimetype2,
586                             prop_changes, original_props)
587      end
588
589      def dir_props_changed(access, path, prop_changes, original_props)
590        prop_changes = Util.hash_to_prop_array(prop_changes)
591        @original.dir_props_changed(access, path, prop_changes, original_props)
592      end
593
594      def method_missing(method, *args, &block)
595        @original.__send__(method, *args, &block)
596      end
597    end
598
599    class TraversalInfo
600      def edited_externals
601        Wc.edited_externals(self)
602      end
603    end
604
605    class Entry
606      def dup
607        Wc.entry_dup(self, Svn::Core::Pool.new)
608      end
609
610      def conflicted(dir_path)
611        Wc.conflicted_p(dir_path, self)
612      end
613
614      def conflicted?(dir_path)
615        conflicted(dir_path).any? {|x| x}
616      end
617
618      def text_conflicted?(dir_path)
619        conflicted(dir_path)[0]
620      end
621
622      def prop_conflicted?(dir_path)
623        conflicted(dir_path)[1]
624      end
625
626      def dir?
627        kind == Core::NODE_DIR
628      end
629
630      def file?
631        kind == Core::NODE_FILE
632      end
633
634      def add?
635        schedule == SCHEDULE_ADD
636      end
637
638      def normal?
639        schedule == SCHEDULE_NORMAL
640      end
641    end
642
643    class Status2
644      def dup
645        Wc.dup_status2(self, Core::Pool.new)
646      end
647
648      def text_added?
649        text_status == STATUS_ADDED
650      end
651
652      def text_normal?
653        text_status == STATUS_NORMAL
654      end
655    end
656
657    class Notify
658      def dup
659        Wc.dup_notify(self, Core::Pool.new)
660      end
661
662      def commit_added?
663        action == NOTIFY_COMMIT_ADDED
664      end
665
666      def commit_deleted?
667        action == NOTIFY_COMMIT_DELETED
668      end
669
670      def commit_postfix_txdelta?
671        action == NOTIFY_COMMIT_POSTFIX_TXDELTA
672      end
673
674      def add?
675        action == NOTIFY_ADD
676      end
677
678      def locked?
679        lock_state = NOTIFY_LOCK_STATE_LOCKED
680      end
681
682      def unlocked?
683        lock_state = NOTIFY_LOCK_STATE_UNLOCKED
684      end
685    end
686
687    class RevisionStatus
688      alias _initialize initialize
689      def initialize(wc_path, trail_url, committed, cancel_func=nil)
690        _initialize(wc_path, trail_url, committed, cancel_func)
691      end
692    end
693
694    class CommittedQueue
695      def push(access, path, recurse=true, wcprop_changes={}, remove_lock=true,
696               remove_changelist=false, digest=nil)
697        Wc.queue_committed(self, path, access, recurse, wcprop_changes,
698                           remove_lock, remove_changelist, digest)
699        self
700      end
701
702      def process(access, new_rev, rev_date=nil, rev_author=nil)
703        rev_date = rev_date.to_svn_format if rev_date.is_a?(Time)
704        Wc.process_committed_queue(self, access, new_rev, rev_date, rev_author)
705      end
706    end
707
708    Context = SWIG::TYPE_p_svn_wc_context_t
709    # A context is not associated with a particular working copy, but as
710    # operations are performed, will load the appropriate working copy
711    # information.
712    class Context
713      class << self
714
715        # Creates an new instance of Context.
716        #
717        # ==== arguments
718        #
719        # * <tt>:config</tt> <i>(default=>nil)</i> A \
720        # Svn::Core::Config with options that apply to this Context.
721        def new(arguments={})
722          optional_arguments_defaults = { :config => nil }
723          arguments = optional_arguments_defaults.merge(arguments)
724          context = Wc.context_create(arguments[:config])
725          return context
726        end
727
728        # Creates an new instance of Context for use in the block, the context
729        # is destroyed when the block completes.
730        #
731        # ==== arguments
732        #
733        # see new.
734        def create(arguments={})
735          context = new(arguments)
736          begin
737            yield context
738          ensure
739            context.destroy if context
740          end
741        end
742
743      end
744
745      # Destroys the context, releasing any acquired resources.
746      # The context is unavailable for any further operations.
747      def destroy
748        Wc.context_destroy(self)
749      end
750    end
751
752  end
753end
754