1#!/usr/bin/env python
2#
3#  changelist_tests.py:  testing changelist uses.
4#
5#  Subversion is a tool for revision control.
6#  See http://subversion.apache.org for more information.
7#
8# ====================================================================
9#    Licensed to the Apache Software Foundation (ASF) under one
10#    or more contributor license agreements.  See the NOTICE file
11#    distributed with this work for additional information
12#    regarding copyright ownership.  The ASF licenses this file
13#    to you under the Apache License, Version 2.0 (the
14#    "License"); you may not use this file except in compliance
15#    with the License.  You may obtain a copy of the License at
16#
17#      http://www.apache.org/licenses/LICENSE-2.0
18#
19#    Unless required by applicable law or agreed to in writing,
20#    software distributed under the License is distributed on an
21#    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
22#    KIND, either express or implied.  See the License for the
23#    specific language governing permissions and limitations
24#    under the License.
25######################################################################
26
27# General modules
28import string, sys, os, re
29
30# Our testing module
31import svntest
32
33# (abbreviation)
34Skip = svntest.testcase.Skip_deco
35SkipUnless = svntest.testcase.SkipUnless_deco
36XFail = svntest.testcase.XFail_deco
37Issues = svntest.testcase.Issues_deco
38Issue = svntest.testcase.Issue_deco
39Wimp = svntest.testcase.Wimp_deco
40Item = svntest.wc.StateItem
41
42
43######################################################################
44# Utilities
45
46
47def mod_all_files(wc_dir, new_text):
48  """Walk over working copy WC_DIR, appending NEW_TEXT to all the
49  files in that tree (but not inside the .svn areas of that tree)."""
50
51  dot_svn = svntest.main.get_admin_name()
52  for dirpath, dirs, files in os.walk(wc_dir):
53    if dot_svn in dirs:
54      dirs.remove(dot_svn)
55    for name in files:
56      svntest.main.file_append(os.path.join(dirpath, name), new_text)
57
58def changelist_all_files(wc_dir, name_func):
59  """Walk over working copy WC_DIR, adding versioned files to
60  changelists named by invoking NAME_FUNC(full-path-of-file) and
61  noting its string return value (or None, if we wish to remove the
62  file from a changelist)."""
63
64  dot_svn = svntest.main.get_admin_name()
65  for dirpath, dirs, files in os.walk(wc_dir):
66    if dot_svn in dirs:
67      dirs.remove(dot_svn)
68    for name in files:
69        full_path = os.path.join(dirpath, name)
70        clname = name_func(full_path)
71        if not clname:
72          svntest.main.run_svn(None, "changelist", "--remove", full_path)
73        else:
74          svntest.main.run_svn(None, "changelist", clname, full_path)
75
76def select_paths(target_path, depth, changelists, name_func):
77  """Return the subset of paths found on disk at TARGET_PATH, to a depth
78     of DEPTH, that match CHANGELISTS.
79     NAME_FUNC, rather than the working copy, determines what
80     changelist each path is associated with.
81     Returned paths are relative to the CWD.
82
83     ### Only the paths of files are returned.
84  """
85  dot_svn = svntest.main.get_admin_name()
86  for dirpath, dirs, files in os.walk(target_path):
87    # prepare to return paths relative to WC_DIR
88    if dot_svn in dirs:
89      dirs.remove(dot_svn)
90    if not changelists:  # When changelists support dirs, add: "or name_func(name) in changelists"
91      yield os.path.normpath(dirpath)
92    if depth == 'empty':
93      dirs[:] = []  # process no subdirs
94      continue      # nor files
95    for name in files:
96      if not changelists or name_func(name) in changelists:
97        yield os.path.normpath(os.path.join(dirpath, name))
98    if depth == 'files':
99      dirs[:] = []  # process no subdirs
100    if depth == 'immediates':
101      depth = 'empty'  # process subdirs, but no files nor dirs in them
102
103def clname_from_lastchar_cb(full_path):
104  """Callback for changelist_all_files() that returns a changelist
105  name matching the last character in the file's name.  For example,
106  after running this on a greek tree where every file has some text
107  modification, 'svn status' shows:
108
109    --- Changelist 'a':
110    M      A/B/lambda
111    M      A/B/E/alpha
112    M      A/B/E/beta
113    M      A/D/gamma
114    M      A/D/H/omega
115    M      iota
116
117    --- Changelist 'u':
118    M      A/mu
119    M      A/D/G/tau
120
121    --- Changelist 'i':
122    M      A/D/G/pi
123    M      A/D/H/chi
124    M      A/D/H/psi
125
126    --- Changelist 'o':
127    M      A/D/G/rho
128    """
129  return full_path[-1]
130
131
132# Regular expressions for 'svn changelist' output.
133_re_cl_rem_pattern = "^D \[(.*)\] (.*)"
134_re_cl_skip = re.compile("Skipped '(.*)'")
135_re_cl_add  = re.compile("^A \[(.*)\] (.*)")
136_re_cl_rem  = re.compile(_re_cl_rem_pattern)
137
138def verify_changelist_output(output, expected_adds=None,
139                             expected_removals=None,
140                             expected_skips=None):
141  """Compare lines of OUTPUT from 'svn changelist' against
142  EXPECTED_ADDS (a dictionary mapping paths to changelist names),
143  EXPECTED_REMOVALS (a dictionary mapping paths to ... whatever), and
144  EXPECTED_SKIPS (a dictionary mapping paths to ... whatever).
145
146  EXPECTED_SKIPS is ignored if None."""
147
148  num_expected = 0
149  if expected_adds:
150    num_expected += len(expected_adds)
151  if expected_removals:
152    num_expected += len(expected_removals)
153  if expected_skips:
154    num_expected += len(expected_skips)
155
156  if not expected_skips:
157    output = [line for line in output if (not _re_cl_skip.match(line))]
158
159  for line in output:
160    line = line.rstrip()
161    match = _re_cl_rem.match(line)
162    if match \
163       and expected_removals \
164       and match.group(2) in expected_removals:
165        continue
166    elif match:
167      raise svntest.Failure("Unexpected changelist removal line: " + line)
168    match = _re_cl_add.match(line)
169    if match \
170       and expected_adds \
171       and expected_adds.get(match.group(2)) == match.group(1):
172        continue
173    elif match:
174      raise svntest.Failure("Unexpected changelist add line: " + line)
175    match = _re_cl_skip.match(line)
176    if match \
177       and expected_skips \
178       and match.group(2) in expected_skips:
179        continue
180    elif match:
181      raise svntest.Failure("Unexpected changelist skip line: " + line)
182    raise svntest.Failure("Unexpected line: " + line)
183
184  if len(output) != num_expected:
185    raise svntest.Failure("Unexpected number of 'svn changelist' output " +
186                          "lines (%d vs %d)" % (len(output), num_expected))
187
188def verify_pget_output(output, expected_props):
189  """Compare lines of OUTPUT from 'svn propget' against EXPECTED_PROPS
190  (a dictionary mapping paths to property values)."""
191
192  _re_pget = re.compile('^(.*) - (.*)$')
193  actual_props = {}
194  for line in output:
195    try:
196      path, prop = line.rstrip().split(' - ')
197    except:
198      raise svntest.Failure("Unexpected output line: " + line)
199    actual_props[path] = prop
200  if expected_props != actual_props:
201    raise svntest.Failure("Got unexpected property results\n"
202                          "\tExpected: %s\n"
203                          "\tActual: %s" % (str(expected_props),
204                                            str(actual_props)))
205
206
207######################################################################
208# Tests
209#
210#   Each test must return on success or raise on failure.
211
212
213#----------------------------------------------------------------------
214
215def add_remove_changelists(sbox):
216  "add and remove files from changelists"
217
218  sbox.build()
219  wc_dir = sbox.wc_dir
220
221  ### 'Skip' notifications
222
223  def expected_skips_under(*greek_path):
224    "return a dict mapping Greek-tree directories below GREEK_PATH to None"
225
226    expected_skips = {}
227    for path in expected_skips_all:
228      if path.startswith(os.path.join(wc_dir, *greek_path)):
229        expected_skips[path] = None
230
231    return expected_skips
232
233  def all_parents(expected_adds):
234    """return a dict mapping Greek-tree directories above directories in
235       EXPECTED_ADDS to None"""
236
237    expected_skips = {}
238    for path in expected_adds.keys():
239      if not os.path.isdir(path):
240        path = os.path.dirname(path)
241
242      while path != wc_dir:
243        expected_skips[path] = None
244        path = os.path.dirname(path)
245
246    expected_skips[wc_dir] = None
247    return expected_skips
248
249  # all dirs in the Greek tree
250  expected_skips_all = dict([(x, None) for x in [
251    sbox.ospath(''),
252    sbox.ospath('A'),
253    sbox.ospath('A/B'),
254    sbox.ospath('A/B/E'),
255    sbox.ospath('A/B/F'),
256    sbox.ospath('A/C'),
257    sbox.ospath('A/D'),
258    sbox.ospath('A/D/G'),
259    sbox.ospath('A/D/H'),
260    ]])
261
262  expected_skips_wc_dir = { wc_dir : None }
263
264  ### First, we play with just adding to changelists ###
265
266  # svn changelist foo WC_DIR
267  exit_code, output, errput = svntest.main.run_svn(None, "changelist", "foo",
268                                                   wc_dir)
269  verify_changelist_output(output) # nothing expected
270
271  # svn changelist foo WC_DIR --depth files
272  exit_code, output, errput = svntest.main.run_svn(None, "changelist", "foo",
273                                                   "--depth", "files",
274                                                   wc_dir)
275  expected_adds = {
276    os.path.join(wc_dir, 'iota') : 'foo',
277    }
278  verify_changelist_output(output, expected_adds)
279
280  # svn changelist foo WC_DIR --depth infinity
281  exit_code, output, errput = svntest.main.run_svn(None, "changelist", "foo",
282                                                   "--depth", "infinity",
283                                                   wc_dir)
284  expected_adds = {
285      sbox.ospath('A/B/E/alpha') : 'foo',
286      sbox.ospath('A/B/E/beta') : 'foo',
287      sbox.ospath('A/B/lambda') : 'foo',
288      sbox.ospath('A/D/G/pi') : 'foo',
289      sbox.ospath('A/D/G/rho') : 'foo',
290      sbox.ospath('A/D/G/tau') : 'foo',
291      sbox.ospath('A/D/H/chi') : 'foo',
292      sbox.ospath('A/D/H/omega') : 'foo',
293      sbox.ospath('A/D/H/psi') : 'foo',
294      sbox.ospath('A/D/gamma') : 'foo',
295      sbox.ospath('A/mu') : 'foo',
296    }
297  verify_changelist_output(output, expected_adds)
298
299  ### Now, change some changelists ###
300
301  # svn changelist bar WC_DIR/A/D --depth infinity
302  exit_code, output, errput = svntest.main.run_svn(".*", "changelist", "bar",
303                                                   "--depth", "infinity",
304                                                   sbox.ospath('A/D'))
305  expected_adds = {
306      sbox.ospath('A/D/G/pi') : 'bar',
307      sbox.ospath('A/D/G/rho') : 'bar',
308      sbox.ospath('A/D/G/tau') : 'bar',
309      sbox.ospath('A/D/H/chi') : 'bar',
310      sbox.ospath('A/D/H/omega') : 'bar',
311      sbox.ospath('A/D/H/psi') : 'bar',
312      sbox.ospath('A/D/gamma') : 'bar',
313    }
314  expected_removals = expected_adds
315  verify_changelist_output(output, expected_adds, expected_removals)
316
317  # svn changelist baz WC_DIR/A/D/H --depth infinity
318  exit_code, output, errput = svntest.main.run_svn(".*", "changelist", "baz",
319                                                   "--depth", "infinity",
320                                                   sbox.ospath('A/D/H'))
321  expected_adds = {
322      sbox.ospath('A/D/H/chi') : 'baz',
323      sbox.ospath('A/D/H/omega') : 'baz',
324      sbox.ospath('A/D/H/psi') : 'baz',
325    }
326  expected_removals = expected_adds
327  verify_changelist_output(output, expected_adds, expected_removals)
328
329  ### Now, let's selectively rename some changelists ###
330
331  # svn changelist foo-rename WC_DIR --depth infinity --changelist foo
332  exit_code, output, errput = svntest.main.run_svn(".*", "changelist",
333                                                   "foo-rename",
334                                                   "--depth", "infinity",
335                                                   "--changelist", "foo",
336                                                   wc_dir)
337  expected_adds = {
338      sbox.ospath('A/B/E/alpha') : 'foo-rename',
339      sbox.ospath('A/B/E/beta') : 'foo-rename',
340      sbox.ospath('A/B/lambda') : 'foo-rename',
341      sbox.ospath('A/mu') : 'foo-rename',
342      sbox.ospath('iota') : 'foo-rename',
343    }
344  expected_removals = expected_adds
345  verify_changelist_output(output, expected_adds, expected_removals)
346
347  # svn changelist bar WC_DIR --depth infinity
348  #     --changelist foo-rename --changelist baz
349  exit_code, output, errput = svntest.main.run_svn(
350    ".*", "changelist", "bar", "--depth", "infinity",
351    "--changelist", "foo-rename", "--changelist", "baz", wc_dir)
352
353  expected_adds = {
354      sbox.ospath('A/B/E/alpha') : 'bar',
355      sbox.ospath('A/B/E/beta') : 'bar',
356      sbox.ospath('A/B/lambda') : 'bar',
357      sbox.ospath('A/D/H/chi') : 'bar',
358      sbox.ospath('A/D/H/omega') : 'bar',
359      sbox.ospath('A/D/H/psi') : 'bar',
360      sbox.ospath('A/mu') : 'bar',
361      sbox.ospath('iota') : 'bar',
362    }
363  expected_removals = expected_adds
364  verify_changelist_output(output, expected_adds, expected_removals)
365
366  ### Okay.  Time to remove some stuff from changelists now. ###
367
368  # svn changelist --remove WC_DIR
369  exit_code, output, errput = svntest.main.run_svn(None, "changelist",
370                                                   "--remove", wc_dir)
371  verify_changelist_output(output) # nothing expected
372
373  # svn changelist --remove WC_DIR --depth files
374  exit_code, output, errput = svntest.main.run_svn(None, "changelist",
375                                                   "--remove",
376                                                   "--depth", "files",
377                                                   wc_dir)
378  expected_removals = {
379    os.path.join(wc_dir, 'iota') : None,
380    }
381  verify_changelist_output(output, None, expected_removals)
382
383  # svn changelist --remove WC_DIR --depth infinity
384  exit_code, output, errput = svntest.main.run_svn(None, "changelist",
385                                                   "--remove",
386                                                   "--depth", "infinity",
387                                                   wc_dir)
388  expected_removals = {
389      sbox.ospath('A/B/E/alpha') : None,
390      sbox.ospath('A/B/E/beta') : None,
391      sbox.ospath('A/B/lambda') : None,
392      sbox.ospath('A/D/G/pi') : None,
393      sbox.ospath('A/D/G/rho') : None,
394      sbox.ospath('A/D/G/tau') : None,
395      sbox.ospath('A/D/H/chi') : None,
396      sbox.ospath('A/D/H/omega') : None,
397      sbox.ospath('A/D/H/psi') : None,
398      sbox.ospath('A/D/gamma') : None,
399      sbox.ospath('A/mu') : None,
400    }
401  verify_changelist_output(output, None, expected_removals)
402
403  ### Add files to changelists based on the last character in their names ###
404
405  changelist_all_files(wc_dir, clname_from_lastchar_cb)
406
407  ### Now, do selective changelist removal ###
408
409  # svn changelist --remove WC_DIR --depth infinity --changelist a
410  exit_code, output, errput = svntest.main.run_svn(None, "changelist",
411                                                   "--remove",
412                                                   "--depth", "infinity",
413                                                   "--changelist", "a",
414                                                   wc_dir)
415  expected_removals = {
416      sbox.ospath('A/B/E/alpha') : None,
417      sbox.ospath('A/B/E/beta') : None,
418      sbox.ospath('A/B/lambda') : None,
419      sbox.ospath('A/D/H/omega') : None,
420      sbox.ospath('A/D/gamma') : None,
421      sbox.ospath('iota') : None,
422    }
423  verify_changelist_output(output, None, expected_removals)
424
425  # svn changelist --remove WC_DIR --depth infinity
426  #     --changelist i --changelist o
427  exit_code, output, errput = svntest.main.run_svn(None, "changelist",
428                                                   "--remove",
429                                                   "--depth", "infinity",
430                                                   "--changelist", "i",
431                                                   "--changelist", "o",
432                                                   wc_dir)
433  expected_removals = {
434      sbox.ospath('A/D/G/pi') : None,
435      sbox.ospath('A/D/G/rho') : None,
436      sbox.ospath('A/D/H/chi') : None,
437      sbox.ospath('A/D/H/psi') : None,
438    }
439  verify_changelist_output(output, None, expected_removals)
440
441#----------------------------------------------------------------------
442
443def commit_one_changelist(sbox):
444  "commit with single --changelist"
445
446  sbox.build()
447  wc_dir = sbox.wc_dir
448
449  # Add a line of text to all the versioned files in the tree.
450  mod_all_files(wc_dir, "New text.\n")
451
452  # Add files to changelists based on the last character in their names.
453  changelist_all_files(wc_dir, clname_from_lastchar_cb)
454
455  # Now, test a commit that uses a single changelist filter (--changelist a).
456  expected_output = svntest.wc.State(wc_dir, {
457    'A/B/lambda' : Item(verb='Sending'),
458    'A/B/E/alpha' : Item(verb='Sending'),
459    'A/B/E/beta' : Item(verb='Sending'),
460    'A/D/gamma' : Item(verb='Sending'),
461    'A/D/H/omega' : Item(verb='Sending'),
462    'iota' : Item(verb='Sending'),
463    })
464  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
465  expected_status.tweak('A/mu', 'A/D/G/tau', 'A/D/G/pi', 'A/D/H/chi',
466                        'A/D/H/psi', 'A/D/G/rho', wc_rev=1, status='M ')
467  expected_status.tweak('iota', 'A/B/lambda', 'A/B/E/alpha', 'A/B/E/beta',
468                        'A/D/gamma', 'A/D/H/omega', wc_rev=2, status='  ')
469  svntest.actions.run_and_verify_commit(wc_dir,
470                                        expected_output,
471                                        expected_status,
472                                        [],
473                                        wc_dir,
474                                        "--changelist",
475                                        "a")
476
477#----------------------------------------------------------------------
478
479def commit_multiple_changelists(sbox):
480  "commit with multiple --changelist's"
481
482  sbox.build()
483  wc_dir = sbox.wc_dir
484
485  # Add a line of text to all the versioned files in the tree.
486  mod_all_files(wc_dir, "New text.\n")
487
488  # Add files to changelists based on the last character in their names.
489  changelist_all_files(wc_dir, clname_from_lastchar_cb)
490
491  # Now, test a commit that uses multiple changelist filters
492  # (--changelist=a --changelist=i).
493  expected_output = svntest.wc.State(wc_dir, {
494    'A/B/lambda' : Item(verb='Sending'),
495    'A/B/E/alpha' : Item(verb='Sending'),
496    'A/B/E/beta' : Item(verb='Sending'),
497    'A/D/gamma' : Item(verb='Sending'),
498    'A/D/H/omega' : Item(verb='Sending'),
499    'iota' : Item(verb='Sending'),
500    'A/D/G/pi' : Item(verb='Sending'),
501    'A/D/H/chi' : Item(verb='Sending'),
502    'A/D/H/psi' : Item(verb='Sending'),
503    })
504  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
505  expected_status.tweak('A/mu', 'A/D/G/tau', 'A/D/G/rho',
506                        wc_rev=1, status='M ')
507  expected_status.tweak('iota', 'A/B/lambda', 'A/B/E/alpha', 'A/B/E/beta',
508                        'A/D/gamma', 'A/D/H/omega', 'A/D/G/pi', 'A/D/H/chi',
509                        'A/D/H/psi', wc_rev=2, status='  ')
510  svntest.actions.run_and_verify_commit(wc_dir,
511                                        expected_output,
512                                        expected_status,
513                                        [],
514                                        wc_dir,
515                                        "--changelist", "a",
516                                        "--changelist", "i")
517
518#----------------------------------------------------------------------
519
520def info_with_changelists(sbox):
521  "info --changelist"
522
523  sbox.build()
524  wc_dir = sbox.wc_dir
525
526  # Add files to changelists based on the last character in their names.
527  changelist_all_files(wc_dir, clname_from_lastchar_cb)
528
529  # Now, test various combinations of changelist specification and depths.
530  for clname in [['a'], ['i'], ['a', 'i']]:
531    for depth in [None, 'files', 'infinity']:
532
533      # Figure out what we expect to see in our info output.
534      expected_paths = []
535      if 'a' in clname:
536        if depth == 'infinity':
537          expected_paths.append('A/B/lambda')
538          expected_paths.append('A/B/E/alpha')
539          expected_paths.append('A/B/E/beta')
540          expected_paths.append('A/D/gamma')
541          expected_paths.append('A/D/H/omega')
542        if depth == 'files' or depth == 'infinity':
543          expected_paths.append('iota')
544      if 'i' in clname:
545        if depth == 'infinity':
546          expected_paths.append('A/D/G/pi')
547          expected_paths.append('A/D/H/chi')
548          expected_paths.append('A/D/H/psi')
549      expected_paths = sorted([os.path.join(wc_dir, x.replace('/', os.sep)) for x in expected_paths])
550
551      # Build the command line.
552      args = ['info', wc_dir]
553      for cl in clname:
554        args.append('--changelist')
555        args.append(cl)
556      if depth:
557        args.append('--depth')
558        args.append(depth)
559
560      # Run 'svn info ...'
561      exit_code, output, errput = svntest.main.run_svn(None, *args)
562
563      # Filter the output for lines that begin with 'Path:', and
564      # reduce even those lines to just the actual path.
565      paths = sorted([x[6:].rstrip() for x in output if x[:6] == 'Path: '])
566
567      # And, compare!
568      if (paths != expected_paths):
569        raise svntest.Failure("Expected paths (%s) and actual paths (%s) "
570                              "don't gel" % (str(expected_paths), str(paths)))
571
572#----------------------------------------------------------------------
573
574@XFail()
575@Issue(4826)
576def diff_with_changelists(sbox):
577  "diff --changelist (wc-wc and repos-wc)"
578
579  sbox.build()
580  wc_dir = sbox.wc_dir
581
582  # Add a line of text to all the versioned files in the tree.
583  mod_all_files(wc_dir, "New text.\n")
584
585  # Also make a property modification on each directory.
586  svntest.main.run_svn(None, 'propset', 'p', 'v', '-R', wc_dir)
587
588  # Add files to changelists based on the last character in their names.
589  changelist_all_files(wc_dir, clname_from_lastchar_cb)
590
591  # Now, test various combinations of changelist specification and depths.
592  for is_repos_wc in [0, 1]:
593    for clname in [['a'], ['a', 'i'], []]:
594      for depth in ['empty', 'files', 'immediates', 'infinity', None]:
595       for subdir in ['.', 'A', 'A/D']:
596
597        # Figure out what we expect to see in our diff output.
598        expected_paths = sorted(select_paths(sbox.ospath(subdir), depth, clname, clname_from_lastchar_cb))
599
600        # Build the command line.
601        args = ['diff']
602        for cl in clname:
603          args.append('--changelist')
604          args.append(cl)
605        if depth:
606          args.append('--depth')
607          args.append(depth)
608        if is_repos_wc:
609          args.append('--old')
610          args.append(sbox.repo_url + '/' + subdir)
611          args.append('--new')
612          args.append(os.path.join(wc_dir, subdir))
613        else:
614          args.append(os.path.join(wc_dir, subdir))
615
616        # Run 'svn diff ...'
617        exit_code, output, errput = svntest.main.run_svn(None, *args)
618
619        # Filter the output for lines that begin with 'Index:', and
620        # reduce even those lines to just the actual path.
621        paths = sorted([x[7:].rstrip() for x in output if x[:7] == 'Index: '])
622
623        # Diff output on Win32 uses '/' path separators.
624        if sys.platform == 'win32':
625          paths = [x.replace('/', os.sep) for x in paths]
626
627        # And, compare!
628        if (paths != expected_paths):
629          raise svntest.Failure("Expected paths (%s) and actual paths (%s) "
630                                "don't gel"
631                                % (str(expected_paths), str(paths)))
632
633#----------------------------------------------------------------------
634
635def propmods_with_changelists(sbox):
636  "propset/del/get/list --changelist"
637
638  sbox.build()
639  wc_dir = sbox.wc_dir
640
641  # Add files to changelists based on the last character in their names.
642  changelist_all_files(wc_dir, clname_from_lastchar_cb)
643
644  # Set property 'name'='value' on all working copy items.
645  svntest.main.run_svn(None, "pset", "--depth", "infinity",
646                       "name", "value", wc_dir)
647  expected_disk = svntest.main.greek_state.copy()
648  expected_disk.add({'' : Item(props={ 'name' : 'value' })})
649  expected_disk.tweak('A', 'A/B', 'A/B/E', 'A/B/E/alpha', 'A/B/E/beta',
650                      'A/B/F', 'A/B/lambda', 'A/C', 'A/D', 'A/D/G',
651                      'A/D/G/pi', 'A/D/G/rho', 'A/D/G/tau', 'A/D/H',
652                      'A/D/H/chi', 'A/D/H/omega', 'A/D/H/psi', 'A/D/gamma',
653                      'A/mu', 'iota', props={ 'name' : 'value' })
654
655  svntest.actions.verify_disk(wc_dir, expected_disk, True)
656
657  # Proplist the 'i' changelist
658  exit_code, output, errput = svntest.main.run_svn(None, "proplist", "--depth",
659                                                   "infinity", "--changelist",
660                                                   "i", wc_dir)
661  ### Really simple sanity check on the output of 'proplist'.  If we've got
662  ### a proper proplist content checker anywhere, we should probably use it
663  ### instead.
664  if len(output) != 6:
665    raise svntest.Failure
666
667  # Remove the 'name' property from files in the 'o' and 'i' changelists.
668  svntest.main.run_svn(None, "pdel", "--depth", "infinity",
669                       "name", "--changelist", "o", "--changelist", "i",
670                       wc_dir)
671  expected_disk.tweak('A/D/G/pi', 'A/D/G/rho', 'A/D/H/chi', 'A/D/H/psi',
672                      props={})
673  svntest.actions.verify_disk(wc_dir, expected_disk, True)
674
675  # Add 'foo'='bar' property on all files under A/B to depth files and
676  # in changelist 'a'.
677  svntest.main.run_svn(None, "pset", "--depth", "files",
678                       "foo", "bar", "--changelist", "a",
679                       os.path.join(wc_dir, 'A', 'B'))
680  expected_disk.tweak('A/B/lambda', props={ 'name' : 'value',
681                                            'foo'  : 'bar' })
682  svntest.actions.verify_disk(wc_dir, expected_disk, True)
683
684  # Add 'bloo'='blarg' property to all files in changelist 'a'.
685  svntest.main.run_svn(None, "pset", "--depth", "infinity",
686                       "bloo", "blarg", "--changelist", "a",
687                       wc_dir)
688  expected_disk.tweak('A/B/lambda', props={ 'name' : 'value',
689                                            'foo'  : 'bar',
690                                            'bloo' : 'blarg' })
691  expected_disk.tweak('A/B/E/alpha', 'A/B/E/beta', 'A/D/H/omega', 'A/D/gamma',
692                      'iota', props={ 'name' : 'value',
693                                      'bloo' : 'blarg' })
694  svntest.actions.verify_disk(wc_dir, expected_disk, True)
695
696  # Propget 'name' in files in changelists 'a' and 'i' to depth files.
697  exit_code, output, errput = svntest.main.run_svn(None, "pget",
698                                                   "--depth", "files", "name",
699                                                   "--changelist", "a",
700                                                   "--changelist", "i",
701                                                   wc_dir)
702  verify_pget_output(output, {
703    os.path.join(wc_dir, 'iota') : 'value',
704    })
705
706  # Propget 'name' in files in changelists 'a' and 'i' to depth infinity.
707  exit_code, output, errput = svntest.main.run_svn(None, "pget",
708                                                   "--depth", "infinity",
709                                                   "name",
710                                                   "--changelist", "a",
711                                                   "--changelist", "i",
712                                                   wc_dir)
713  verify_pget_output(output, {
714    os.path.join(wc_dir, 'A', 'D', 'gamma')      : 'value',
715    os.path.join(wc_dir, 'A', 'B', 'E', 'alpha') : 'value',
716    os.path.join(wc_dir, 'iota')                 : 'value',
717    os.path.join(wc_dir, 'A', 'B', 'E', 'beta')  : 'value',
718    os.path.join(wc_dir, 'A', 'B', 'lambda')     : 'value',
719    os.path.join(wc_dir, 'A', 'D', 'H', 'omega') : 'value',
720    })
721
722
723#----------------------------------------------------------------------
724
725def revert_with_changelists(sbox):
726  "revert --changelist"
727
728  sbox.build()
729  wc_dir = sbox.wc_dir
730
731  # Add files to changelists based on the last character in their names.
732  changelist_all_files(wc_dir, clname_from_lastchar_cb)
733
734  # Add a line of text to all the versioned files in the tree.
735  mod_all_files(wc_dir, "Please, oh please, revert me!\n")
736  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
737  expected_status.tweak('A/B/lambda', 'A/B/E/alpha', 'A/B/E/beta',
738                        'A/D/gamma', 'A/D/H/omega', 'iota', 'A/mu',
739                        'A/D/G/tau', 'A/D/G/pi', 'A/D/H/chi',
740                        'A/D/H/psi', 'A/D/G/rho', status='M ')
741  svntest.actions.run_and_verify_status(wc_dir, expected_status)
742
743  # 'svn revert --changelist a WC_DIR' (without depth, no change expected)
744  svntest.main.run_svn(None, "revert", "--changelist", "a", wc_dir)
745  svntest.actions.run_and_verify_status(wc_dir, expected_status)
746
747  # 'svn revert --changelist o --depth files WC_DIR WC_DIR/A/B' (no change)
748  svntest.main.run_svn(None, "revert", "--depth", "files",
749                       "--changelist", "o",
750                       wc_dir, os.path.join(wc_dir, 'A', 'B'))
751  svntest.actions.run_and_verify_status(wc_dir, expected_status)
752
753  # 'svn revert --changelist a --depth files WC_DIR WC_DIR/A/B'
754  # (iota, lambda reverted)
755  svntest.main.run_svn(None, "revert", "--depth", "files",
756                       "--changelist", "a",
757                       wc_dir, os.path.join(wc_dir, 'A', 'B'))
758  expected_status.tweak('iota', 'A/B/lambda', status='  ')
759  svntest.actions.run_and_verify_status(wc_dir, expected_status)
760
761  # 'svn revert --changelist a --changelist i --depth infinity WC_DIR'
762  # (alpha, beta, gamma, omega, pi, chi, psi reverted)
763  svntest.main.run_svn(None, "revert", "--depth", "infinity",
764                       "--changelist", "a", "--changelist", "i",
765                       wc_dir)
766  expected_status.tweak('A/B/E/alpha', 'A/B/E/beta', 'A/D/gamma',
767                        'A/D/H/omega', 'A/D/G/pi', 'A/D/H/chi',
768                        'A/D/H/psi', status='  ')
769  svntest.actions.run_and_verify_status(wc_dir, expected_status)
770
771  # 'svn revert --depth infinity WC_DIR' (back to pristine-ness)
772  svntest.main.run_svn(None, "revert", "--depth", "infinity",
773                       wc_dir)
774  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
775  svntest.actions.run_and_verify_status(wc_dir, expected_status)
776
777#----------------------------------------------------------------------
778
779def update_with_changelists(sbox):
780  "update --changelist"
781
782  sbox.build()
783  wc_dir = sbox.wc_dir
784
785  # Add a line of text to all the versioned files in the tree, commit, update.
786  mod_all_files(wc_dir, "Added line.\n")
787  svntest.main.run_svn(None, "commit", "-m", "logmsg", wc_dir)
788  svntest.main.run_svn(None, "update", wc_dir)
789
790  # Add files to changelists based on the last character in their names.
791  changelist_all_files(wc_dir, clname_from_lastchar_cb)
792
793  ### Backdate only the files in the 'a' and 'i' changelists at depth
794  ### files under WC_DIR and WC_DIR/A/B.
795
796  # We expect update to only touch lambda and iota.
797  expected_output = svntest.wc.State(wc_dir, {
798    'A/B/lambda' : Item(status='U '),
799    'iota' : Item(status='U '),
800    })
801
802  # Disk state should have all the files except iota and lambda
803  # carrying new text.
804  expected_disk = svntest.main.greek_state.copy()
805  expected_disk.tweak('A/B/E/alpha',
806                      contents="This is the file 'alpha'.\nAdded line.\n")
807  expected_disk.tweak('A/B/E/beta',
808                      contents="This is the file 'beta'.\nAdded line.\n")
809  expected_disk.tweak('A/D/gamma',
810                      contents="This is the file 'gamma'.\nAdded line.\n")
811  expected_disk.tweak('A/D/H/omega',
812                      contents="This is the file 'omega'.\nAdded line.\n")
813  expected_disk.tweak('A/mu',
814                      contents="This is the file 'mu'.\nAdded line.\n")
815  expected_disk.tweak('A/D/G/tau',
816                      contents="This is the file 'tau'.\nAdded line.\n")
817  expected_disk.tweak('A/D/G/pi',
818                      contents="This is the file 'pi'.\nAdded line.\n")
819  expected_disk.tweak('A/D/H/chi',
820                      contents="This is the file 'chi'.\nAdded line.\n")
821  expected_disk.tweak('A/D/H/psi',
822                      contents="This is the file 'psi'.\nAdded line.\n")
823  expected_disk.tweak('A/D/G/rho',
824                      contents="This is the file 'rho'.\nAdded line.\n")
825
826  # Status is clean, but with iota and lambda at r1 and all else at r2.
827  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
828  expected_status.tweak('iota', 'A/B/lambda', wc_rev=1)
829
830  # Update.
831  svntest.actions.run_and_verify_update(wc_dir,
832                                        expected_output,
833                                        expected_disk,
834                                        expected_status,
835                                        [], True,
836                                        "-r", "1",
837                                        "--changelist", "a",
838                                        "--changelist", "i",
839                                        "--depth", "files",
840                                        wc_dir,
841                                        os.path.join(wc_dir, 'A', 'B'))
842
843  ### Backdate to depth infinity all changelists "a", "i", and "o" now.
844
845  # We expect update to only touch all the files ending in 'a', 'i',
846  # and 'o' (except lambda and iota which were previously updated).
847  expected_output = svntest.wc.State(wc_dir, {
848    'A/D/G/pi' : Item(status='U '),
849    'A/D/H/chi' : Item(status='U '),
850    'A/D/H/psi' : Item(status='U '),
851    'A/D/G/rho' : Item(status='U '),
852    'A/B/E/alpha' : Item(status='U '),
853    'A/B/E/beta' : Item(status='U '),
854    'A/D/gamma' : Item(status='U '),
855    'A/D/H/omega' : Item(status='U '),
856    })
857
858  # Disk state should have only tau and mu carrying new text.
859  expected_disk = svntest.main.greek_state.copy()
860  expected_disk.tweak('A/mu',
861                      contents="This is the file 'mu'.\nAdded line.\n")
862  expected_disk.tweak('A/D/G/tau',
863                      contents="This is the file 'tau'.\nAdded line.\n")
864
865  # Status is clean, but with iota and lambda at r1 and all else at r2.
866  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
867  expected_status.tweak('iota', 'A/B/lambda', 'A/D/G/pi', 'A/D/H/chi',
868                        'A/D/H/psi', 'A/D/G/rho', 'A/B/E/alpha',
869                        'A/B/E/beta', 'A/D/gamma', 'A/D/H/omega', wc_rev=1)
870
871  # Update.
872  svntest.actions.run_and_verify_update(wc_dir,
873                                        expected_output,
874                                        expected_disk,
875                                        expected_status,
876                                        [], True,
877                                        "-r", "1",
878                                        "--changelist", "a",
879                                        "--changelist", "i",
880                                        "--changelist", "o",
881                                        "--depth", "infinity",
882                                        wc_dir)
883
884def tree_conflicts_and_changelists_on_commit1(sbox):
885  "tree conflicts, changelists and commit"
886  svntest.actions.build_greek_tree_conflicts(sbox)
887  wc_dir = sbox.wc_dir
888
889  iota = os.path.join(wc_dir, "iota")
890  rho = os.path.join(wc_dir, "A", "D", "G", "rho")
891
892  # This file will ultimately be committed
893  svntest.main.file_append(iota, "More stuff in iota")
894
895  # Verify that the commit is blocked when we include a tree-conflicted
896  # item.
897  svntest.main.run_svn(None, "changelist", "list", iota, rho)
898
899  expected_error = ("svn: E155015: Aborting commit: '.*" + re.escape(rho)
900                    + "' remains in .*conflict")
901
902  svntest.actions.run_and_verify_commit(wc_dir,
903                                        None, None,
904                                        expected_error,
905                                        wc_dir,
906                                        "--changelist",
907                                        "list")
908
909  # Now, test if we can commit iota without those tree-conflicts
910  # getting in the way.
911  svntest.main.run_svn(None, "changelist", "--remove", rho)
912
913  expected_output = svntest.wc.State(wc_dir, {
914    'iota' : Item(verb='Sending'),
915    })
916  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
917  expected_status.tweak('A/D/G/pi', status='D ', treeconflict='C')
918  expected_status.tweak('A/D/G/tau', status='! ', treeconflict='C',
919                        wc_rev=None)
920  expected_status.tweak('A/D/G/rho', status='A ', copied='+',
921                        treeconflict='C', wc_rev='-')
922  expected_status.tweak('iota', wc_rev=3, status='  ')
923  svntest.actions.run_and_verify_commit(wc_dir,
924                                        expected_output,
925                                        expected_status,
926                                        [],
927                                        wc_dir,
928                                        "--changelist",
929                                        "list")
930
931
932def tree_conflicts_and_changelists_on_commit2(sbox):
933  "more tree conflicts, changelists and commit"
934
935  sbox.build()
936  wc_dir = sbox.wc_dir
937
938  iota = os.path.join(wc_dir, "iota")
939  A = os.path.join(wc_dir, "A",)
940  C = os.path.join(A, "C")
941
942  # Make a tree-conflict on A/C:
943  # Remove it, warp back, add a prop, update.
944  svntest.main.run_svn(None, 'delete', C)
945
946  expected_output = svntest.verify.RegexOutput(
947                                     "Deleting.*" + re.escape(C),
948                                     False)
949  svntest.actions.run_and_verify_svn(expected_output, [],
950                                     'commit', '-m', 'delete A/C', C)
951
952  expected_output = svntest.verify.RegexOutput(
953                                     "A.*" + re.escape(C), False)
954  svntest.actions.run_and_verify_svn(expected_output, [],
955                                     'update', C, "-r1")
956
957  expected_output = svntest.verify.RegexOutput(
958                                     ".*'propname' set on '"
959                                     + re.escape(C) + "'", False)
960  svntest.actions.run_and_verify_svn(expected_output, [],
961                                     'propset', 'propname', 'propval', C)
962
963  expected_output = svntest.verify.RegexOutput(
964                                     "   C " + re.escape(C), False)
965  svntest.actions.run_and_verify_svn(expected_output, [],
966                                     'update', wc_dir)
967
968
969  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
970  expected_status.tweak('A/C', status='A ', copied='+',
971                        treeconflict='C', wc_rev='-')
972
973  svntest.actions.run_and_verify_status(wc_dir, expected_status)
974
975  # So far so good. We have a tree-conflict on an absent dir A/C.
976
977  # Verify that the current situation does not commit.
978  expected_error = "svn: E155015: Aborting commit:.* remains in .*conflict";
979
980  svntest.actions.run_and_verify_commit(wc_dir,
981                                        None, None,
982                                        expected_error,
983                                        wc_dir)
984
985  # Now try to commit with a changelist, not letting the
986  # tree-conflict get in the way.
987  svntest.main.file_append(iota, "More stuff in iota")
988  svntest.main.run_svn(None, "changelist", "list", iota)
989
990  expected_output = svntest.wc.State(wc_dir, {
991    'iota' : Item(verb='Sending'),
992    })
993
994  expected_status.tweak('iota', wc_rev=3, status='  ')
995
996  svntest.actions.run_and_verify_commit(wc_dir,
997                                        expected_output,
998                                        expected_status,
999                                        [],
1000                                        wc_dir,
1001                                        "--changelist",
1002                                        "list")
1003
1004
1005#----------------------------------------------------------------------
1006
1007def move_keeps_changelist(sbox):
1008  "'svn mv' of existing keeps the changelist"
1009
1010  sbox.build(read_only = True)
1011  wc_dir = sbox.wc_dir
1012  iota_path  = os.path.join(wc_dir, 'iota')
1013  iota2_path = iota_path + '2'
1014
1015  # 'svn mv' of existing file should *copy* the changelist to the new place
1016  svntest.main.run_svn(None, "changelist", 'foo', iota_path)
1017  svntest.main.run_svn(None, "rename", iota_path, iota2_path)
1018  expected_infos = [
1019    {
1020      'Name' : 'iota',
1021      'Schedule' : 'delete',
1022      'Changelist' : 'foo',
1023    },
1024    {
1025      'Name' : 'iota2',
1026      'Schedule' : 'add',
1027      'Changelist' : 'foo',  # this line fails the test
1028    },
1029  ]
1030  svntest.actions.run_and_verify_info(expected_infos, iota_path, iota2_path)
1031
1032def move_added_keeps_changelist(sbox):
1033  "'svn mv' of added keeps the changelist"
1034  sbox.build(read_only = True)
1035  wc_dir = sbox.wc_dir
1036  repo_url = sbox.repo_url
1037
1038  kappa_path  = os.path.join(wc_dir, 'kappa')
1039  kappa2_path = kappa_path + '2'
1040
1041  # add 'kappa' (do not commit!)
1042  svntest.main.file_write(kappa_path, "This is the file 'kappa'.\n")
1043  svntest.main.run_svn(None, 'add', kappa_path)
1044
1045  # 'svn mv' of added file should *move* the changelist to the new place
1046  svntest.main.run_svn(None, "changelist", 'foo', kappa_path)
1047  svntest.main.run_svn(None, "rename", kappa_path, kappa2_path)
1048
1049  # kappa not under version control
1050  svntest.actions.run_and_verify_svnversion(kappa_path, repo_url,
1051                                            [], ".*doesn't exist.*")
1052  # kappa2 in a changelist
1053  expected_infos = [
1054    {
1055      'Name' : 'kappa2',
1056      'Schedule' : 'add',
1057      'Changelist' : 'foo',  # this line fails the test
1058    },
1059  ]
1060  svntest.actions.run_and_verify_info(expected_infos, kappa2_path)
1061
1062@Issue(3820)
1063def change_to_dir(sbox):
1064  "change file in changelist to dir"
1065
1066  sbox.build()
1067
1068  # No changelist initially
1069  expected_infos = [{'Name' : 'mu', 'Changelist' : None}]
1070  svntest.actions.run_and_verify_info(expected_infos, sbox.ospath('A/mu'))
1071
1072  # A/mu visible in changelist
1073  svntest.actions.run_and_verify_svn(None, [],
1074                                     'changelist', 'qq', sbox.ospath('A/mu'))
1075  expected_infos = [{'Name' : 'mu', 'Changelist' : 'qq'}]
1076  svntest.actions.run_and_verify_info(expected_infos, sbox.ospath('A/mu'))
1077
1078  # A/mu still visible after delete
1079  svntest.actions.run_and_verify_svn(None, [], 'rm', sbox.ospath('A/mu'))
1080  svntest.actions.run_and_verify_info(expected_infos, sbox.ospath('A/mu'))
1081
1082  # A/mu removed from changelist after replace with directory
1083  svntest.actions.run_and_verify_svn('^A|' + _re_cl_rem_pattern, [],
1084                                     'mkdir', sbox.ospath('A/mu'))
1085  expected_infos = [{'Changelist' : None}] # No Name for directories?
1086  svntest.actions.run_and_verify_info(expected_infos, sbox.ospath('A/mu'))
1087
1088  svntest.main.run_svn(None, "commit", "-m", "r2: replace A/mu: file->dir",
1089                       sbox.ospath('A'))
1090  svntest.actions.run_and_verify_info(expected_infos, sbox.ospath('A/mu'))
1091
1092  svntest.main.run_svn(None, "update", "-r", "1", sbox.ospath('A'))
1093  expected_infos = [{'Name' : 'mu', 'Changelist' : None}]
1094  svntest.actions.run_and_verify_info(expected_infos, sbox.ospath('A/mu'))
1095
1096  # A/mu visible in changelist
1097  svntest.actions.run_and_verify_svn(None, [],
1098                                     'changelist', 'qq', sbox.ospath('A/mu'))
1099  expected_infos = [{'Name' : 'mu', 'Changelist' : 'qq'}]
1100  svntest.actions.run_and_verify_info(expected_infos, sbox.ospath('A/mu'))
1101
1102  # A/mu removed from changelist after replace with dir via merge
1103  svntest.main.run_svn(None, "merge", "-c", "2", sbox.ospath('A'),
1104                       sbox.ospath('A'))
1105  expected_infos = [{'Changelist' : None}] # No Name for directories?
1106  svntest.actions.run_and_verify_info(expected_infos, sbox.ospath('A/mu'))
1107
1108
1109@Issue(3822)
1110def revert_deleted_in_changelist(sbox):
1111  "revert a deleted file in a changelist"
1112
1113  sbox.build(read_only = True)
1114
1115  # No changelist initially
1116  expected_infos = [{'Name' : 'mu', 'Changelist' : None}]
1117  svntest.actions.run_and_verify_info(expected_infos, sbox.ospath('A/mu'))
1118
1119  # A/mu visible in changelist
1120  svntest.actions.run_and_verify_svn(None, [],
1121                                     'changelist', 'qq', sbox.ospath('A/mu'))
1122  expected_infos = [{'Name' : 'mu', 'Changelist' : 'qq'}]
1123  svntest.actions.run_and_verify_info(expected_infos, sbox.ospath('A/mu'))
1124
1125  # A/mu still visible after delete
1126  svntest.actions.run_and_verify_svn(None, [], 'rm', sbox.ospath('A/mu'))
1127  svntest.actions.run_and_verify_info(expected_infos, sbox.ospath('A/mu'))
1128
1129  # A/mu still visible after revert
1130  svntest.actions.run_and_verify_svn(None, [],
1131                                     'revert', sbox.ospath('A/mu'))
1132  svntest.actions.run_and_verify_info(expected_infos, sbox.ospath('A/mu'))
1133
1134  # A/mu still visible after parent delete
1135  svntest.actions.run_and_verify_svn(None, [], 'rm', sbox.ospath('A'))
1136  svntest.actions.run_and_verify_info(expected_infos, sbox.ospath('A/mu'))
1137
1138  # A/mu still visible after revert
1139  svntest.actions.run_and_verify_svn(None, [],
1140                                     'revert', '-R', sbox.ospath('A'))
1141  svntest.actions.run_and_verify_info(expected_infos, sbox.ospath('A/mu'))
1142
1143def add_remove_non_existent_target(sbox):
1144  "add and remove non-existent target to changelist"
1145
1146  sbox.build(read_only = True)
1147  wc_dir = sbox.wc_dir
1148  bogus_path = os.path.join(wc_dir, 'A', 'bogus')
1149
1150  expected_err = "svn: warning: W155010: The node '" + \
1151                 re.escape(os.path.abspath(bogus_path)) + \
1152                 "' was not found"
1153
1154  svntest.actions.run_and_verify_svn(None, expected_err,
1155                                     'changelist', 'testlist',
1156                                     bogus_path)
1157
1158  svntest.actions.run_and_verify_svn(None, expected_err,
1159                                     'changelist', bogus_path,
1160                                      '--remove')
1161
1162def add_remove_unversioned_target(sbox):
1163  "add and remove unversioned target to changelist"
1164
1165  sbox.build(read_only = True)
1166  unversioned = sbox.ospath('unversioned')
1167  svntest.main.file_write(unversioned, "dummy contents", 'w+')
1168
1169  expected_err = "svn: warning: W155010: The node '" + \
1170                 re.escape(os.path.abspath(unversioned)) + \
1171                 "' was not found"
1172
1173  svntest.actions.run_and_verify_svn(None, expected_err,
1174                                     'changelist', 'testlist',
1175                                     unversioned)
1176
1177  svntest.actions.run_and_verify_svn(None, expected_err,
1178                                     'changelist', unversioned,
1179                                      '--remove')
1180
1181@Issue(3985)
1182def readd_after_revert(sbox):
1183  "add new file to changelist, revert and readd"
1184  sbox.build(read_only = True)
1185
1186  dummy = sbox.ospath('dummy')
1187  svntest.main.file_write(dummy, "dummy contents")
1188
1189  sbox.simple_add('dummy')
1190  svntest.actions.run_and_verify_svn(None, [],
1191                                     'changelist', 'testlist',
1192                                     dummy)
1193
1194  sbox.simple_revert('dummy')
1195
1196  svntest.main.file_write(dummy, "dummy contents")
1197
1198  svntest.actions.run_and_verify_svn(None, [],
1199                                     'add', dummy)
1200
1201#----------------------------------------------------------------------
1202
1203# A wc-wc diff returned no results if changelists were specified and the
1204# diff target dir was not the WC root.
1205@Issue(4822)
1206def diff_with_changelists_subdir(sbox):
1207  "diff --changelist (wc-wc) in subdir of WC"
1208
1209  sbox.build()
1210  wc_dir = sbox.wc_dir
1211
1212  expected_paths = sbox.ospaths(['A/D/gamma'])
1213  subdir = 'A/D'
1214  clname = 'a'
1215
1216  for path in expected_paths:
1217    svntest.main.file_append(path, "New text.\n")
1218  svntest.main.run_svn(None, "changelist", clname, *expected_paths)
1219
1220  # Run 'svn diff ...'
1221  exit_code, output, errput = svntest.main.run_svn(None,
1222                                'diff', '--changelist', clname,
1223                                sbox.ospath(subdir))
1224
1225  # Filter the output for lines that begin with 'Index:', and
1226  # reduce even those lines to just the actual path.
1227  paths = sorted([x[7:].rstrip() for x in output if x[:7] == 'Index: '])
1228
1229  # Diff output on Win32 uses '/' path separators.
1230  if sys.platform == 'win32':
1231    paths = [x.replace('/', os.sep) for x in paths]
1232
1233  # And, compare!
1234  if (paths != expected_paths):
1235    raise svntest.Failure("Expected paths (%s) and actual paths (%s) "
1236                          "don't gel"
1237                          % (str(expected_paths), str(paths)))
1238
1239
1240########################################################################
1241# Run the tests
1242
1243# list all tests here, starting with None:
1244test_list = [ None,
1245              add_remove_changelists,
1246              commit_one_changelist,
1247              commit_multiple_changelists,
1248              info_with_changelists,
1249              diff_with_changelists,
1250              propmods_with_changelists,
1251              revert_with_changelists,
1252              update_with_changelists,
1253              tree_conflicts_and_changelists_on_commit1,
1254              tree_conflicts_and_changelists_on_commit2,
1255              move_keeps_changelist,
1256              move_added_keeps_changelist,
1257              change_to_dir,
1258              revert_deleted_in_changelist,
1259              add_remove_non_existent_target,
1260              add_remove_unversioned_target,
1261              readd_after_revert,
1262              diff_with_changelists_subdir,
1263             ]
1264
1265if __name__ == '__main__':
1266  svntest.main.run_tests(test_list)
1267  # NOTREACHED
1268
1269
1270### End of file.
1271