1 /**
2  * @copyright
3  * ====================================================================
4  *    Licensed to the Apache Software Foundation (ASF) under one
5  *    or more contributor license agreements.  See the NOTICE file
6  *    distributed with this work for additional information
7  *    regarding copyright ownership.  The ASF licenses this file
8  *    to you under the Apache License, Version 2.0 (the
9  *    "License"); you may not use this file except in compliance
10  *    with the License.  You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  *    Unless required by applicable law or agreed to in writing,
15  *    software distributed under the License is distributed on an
16  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  *    KIND, either express or implied.  See the License for the
18  *    specific language governing permissions and limitations
19  *    under the License.
20  * ====================================================================
21  * @endcopyright
22  */
23 package org.apache.subversion.javahl;
24 
25 import static org.junit.Assert.*;
26 
27 import org.apache.subversion.javahl.callback.*;
28 import org.apache.subversion.javahl.remote.*;
29 import org.apache.subversion.javahl.types.*;
30 
31 import java.io.File;
32 import java.io.FileOutputStream;
33 import java.io.FileNotFoundException;
34 import java.io.FileReader;
35 import java.io.IOException;
36 import java.io.PrintWriter;
37 import java.io.ByteArrayOutputStream;
38 import java.io.UnsupportedEncodingException;
39 import java.nio.ByteBuffer;
40 import java.nio.channels.ClosedChannelException;
41 import java.nio.channels.ReadableByteChannel;
42 import java.nio.channels.WritableByteChannel;
43 import java.text.ParseException;
44 import java.util.Collection;
45 import java.util.Arrays;
46 import java.util.ArrayList;
47 import java.util.Date;
48 import java.util.HashMap;
49 import java.util.HashSet;
50 import java.util.List;
51 import java.util.Set;
52 import java.util.Map;
53 import java.text.DateFormat;
54 import java.text.SimpleDateFormat;
55 
56 
57 /**
58  * Tests the basic functionality of javahl binding (inspired by the
59  * tests in subversion/tests/cmdline/basic_tests.py).
60  */
61 public class BasicTests extends SVNTests
62 {
63     /**
64      * Base name of all our tests.
65      */
66     public final static String testName = "basic_test";
67 
BasicTests()68     public BasicTests()
69     {
70         init();
71     }
72 
BasicTests(String name)73     public BasicTests(String name)
74     {
75         super(name);
76         init();
77     }
78 
79     /**
80      * Initialize the testBaseName and the testCounter, if this is the
81      * first test of this class.
82      */
init()83     private void init()
84     {
85         if (!testName.equals(testBaseName))
86         {
87             testCounter = 0;
88             testBaseName = testName;
89         }
90     }
91 
92     /**
93      * Test LogDate().
94      * @throws Throwable
95      */
testLogDate()96     public void testLogDate() throws Throwable
97     {
98         String goodDate = "2007-10-04T03:00:52.134992Z";
99         String badDate = "2008-01-14";
100         LogDate logDate;
101 
102         try
103         {
104             logDate = new LogDate(goodDate);
105             assertEquals(1191466852134992L, logDate.getTimeMicros());
106         } catch (ParseException e) {
107             fail("Failed to parse date " + goodDate);
108         }
109 
110         try
111         {
112             logDate = new LogDate(badDate);
113             fail("Failed to throw exception on bad date " + badDate);
114         } catch (ParseException e) {
115         }
116     }
117 
118     /**
119      * Test SVNClient.getVersion().
120      * @throws Throwable
121      */
testVersion()122     public void testVersion() throws Throwable
123     {
124         try
125         {
126             Version version = client.getVersion();
127             String versionString = version.toString();
128             if (versionString == null || versionString.trim().length() == 0)
129             {
130                 throw new Exception("Version string empty");
131             }
132         }
133         catch (Exception e)
134         {
135             fail("Version should always be available unless the " +
136                  "native libraries failed to initialize: " + e);
137         }
138     }
139 
140     /**
141      * Test SVNClient.getVersionExtended().
142      * @throws Throwable
143      */
testVersionExtendedQuiet()144     public void testVersionExtendedQuiet() throws Throwable
145     {
146         VersionExtended vx = null;
147         try
148         {
149             vx = client.getVersionExtended(false);
150             String result = vx.getBuildDate();
151             if (result == null || result.trim().length() == 0)
152                 throw new Exception("Build date empty");
153             result = vx.getBuildTime();
154             if (result == null || result.trim().length() == 0)
155                 throw new Exception("Build time empty");
156             result = vx.getBuildHost();
157             if (result == null || result.trim().length() == 0)
158                 throw new Exception("Build host empty");
159             result = vx.getCopyright();
160             if (result == null || result.trim().length() == 0)
161                 throw new Exception("Copyright empty");
162         }
163         catch (Exception e)
164         {
165             fail("VersionExtended should always be available unless the " +
166                  "native libraries failed to initialize: " + e);
167         }
168         finally
169         {
170             if (vx != null)
171                 vx.dispose();
172         }
173     }
174 
175     /**
176      * Test SVNClient.getVersionExtended().
177      * @throws Throwable
178      */
testVersionExtendedVerbose()179     public void testVersionExtendedVerbose() throws Throwable
180     {
181         VersionExtended vx = null;
182         try
183         {
184             vx = client.getVersionExtended(true);
185             String result = vx.getRuntimeHost();
186             if (result == null || result.trim().length() == 0)
187                 throw new Exception("Runtime host empty");
188 
189             // OS name is allowed to be null, but not empty
190             result = vx.getRuntimeOSName();
191             if (result != null && result.trim().length() == 0)
192                 throw new Exception("Runtime OS name empty");
193 
194             java.util.Iterator<VersionExtended.LinkedLib> ikl;
195             ikl = vx.getLinkedLibs();
196             if (ikl.hasNext())
197             {
198                 VersionExtended.LinkedLib lib = ikl.next();
199                 result = lib.getName();
200                 if (result == null || result.trim().length() == 0)
201                     throw new Exception("Linked lib name empty");
202                 result = lib.getCompiledVersion();
203                 if (result == null || result.trim().length() == 0)
204                     throw new Exception("Linked lib compiled version empty");
205                 // Runtime version is allowed to be null, but not empty
206                 result = lib.getRuntimeVersion();
207                 if (result != null && result.trim().length() == 0)
208                     throw new Exception("Linked lib runtime version empty");
209             }
210 
211             java.util.Iterator<VersionExtended.LoadedLib> ill;
212             ill = vx.getLoadedLibs();
213             if (ill.hasNext())
214             {
215                 VersionExtended.LoadedLib lib = ill.next();
216                 result = lib.getName();
217                 if (result == null || result.trim().length() == 0)
218                     throw new Exception("Loaded lib name empty");
219                 // Version is allowed to be null, but not empty
220                 result = lib.getVersion();
221                 if (result != null && result.trim().length() == 0)
222                     throw new Exception("Loaded lib version empty");
223             }
224         }
225         catch (Exception e)
226         {
227             fail("VersionExtended should always be available unless the " +
228                  "native libraries failed to initialize: " + e);
229         }
230         finally
231         {
232             if (vx != null)
233                 vx.dispose();
234         }
235     }
236 
237     /**
238      * Test RuntimeVersion
239      */
testRuntimeVersion()240     public void testRuntimeVersion() throws Throwable
241     {
242         try
243         {
244             RuntimeVersion runtimeVersion = client.getRuntimeVersion();
245             String versionString = runtimeVersion.toString();
246             if (versionString == null || versionString.trim().length() == 0)
247             {
248                 throw new Exception("Version string empty");
249             }
250         }
251         catch (Exception e)
252         {
253             fail("RuntimeVersion should always be available unless the " +
254                  "native libraries failed to initialize: " + e);
255         }
256 
257         RuntimeVersion runtimeVersion = client.getRuntimeVersion();
258         Version version = client.getVersion();
259         assertTrue(runtimeVersion.getMajor() > version.getMajor()
260                    || (runtimeVersion.getMajor() == version.getMajor()
261                        && runtimeVersion.getMinor() >= version.getMinor()));
262     }
263 
264     /**
265      * Test the JNIError class functionality
266      * @throws Throwable
267      */
testJNIError()268     public void testJNIError() throws Throwable
269     {
270         // build the test setup.
271         OneTest thisTest = new OneTest();
272 
273         // Create a client, dispose it, then try to use it later
274         ISVNClient tempclient = new SVNClient();
275         tempclient.dispose();
276 
277         // create Y and Y/Z directories in the repository
278         addExpectedCommitItem(null, thisTest.getUrl().toString(), "Y", NodeKind.dir,
279                               CommitItemStateFlags.Add);
280         Set<String> urls = new HashSet<String>(1);
281         urls.add(thisTest.getUrl() + "/Y");
282         try
283         {
284             tempclient.mkdir(urls, false, null, new ConstMsg("log_msg"), null);
285         }
286         catch(JNIError e)
287         {
288             return; // Test passes!
289         }
290         fail("A JNIError should have been thrown here.");
291     }
292 
293     /**
294      * Tests Mergeinfo and RevisionRange classes.
295      * @since 1.5
296      */
testMergeinfoParser()297     public void testMergeinfoParser() throws Throwable
298     {
299         String mergeInfoPropertyValue =
300             "/trunk:1-300,305*,307,400-405*\n" +
301             "/branches/branch:308-400";
302         Mergeinfo info = new Mergeinfo(mergeInfoPropertyValue);
303         Set<String> paths = info.getPaths();
304         assertEquals(2, paths.size());
305         List<RevisionRange> trunkRange = info.getRevisionRange("/trunk");
306         assertEquals(4, trunkRange.size());
307         assertEquals("1-300", trunkRange.get(0).toString());
308         assertEquals("305*", trunkRange.get(1).toString());
309         assertEquals("307", trunkRange.get(2).toString());
310         assertEquals("400-405*", trunkRange.get(3).toString());
311         List<RevisionRange> branchRange =
312             info.getRevisionRange("/branches/branch");
313         assertEquals(1, branchRange.size());
314     }
315 
316     /**
317      * Test the basic SVNClient.status functionality.
318      * @throws Throwable
319      */
testBasicStatus()320     public void testBasicStatus() throws Throwable
321     {
322         // build the test setup
323         OneTest thisTest = new OneTest();
324 
325         // check the status of the working copy
326         thisTest.getWc().setItemDepth("", Depth.infinity);
327         thisTest.getWc().setItemDepth("iota", Depth.unknown);
328         thisTest.checkStatus();
329 
330         // Test status of non-existent file
331         File fileC = new File(thisTest.getWorkingCopy() + "/A", "foo.c");
332 
333         MyStatusCallback statusCallback = new MyStatusCallback();
334         client.status(fileToSVNPath(fileC, false), Depth.unknown,
335                       false, true, true, false, false, false,
336                       null, statusCallback);
337 
338         final int statusCount = statusCallback.getStatusArray().length;
339         if (statusCount == 1)
340         {
341             Status st = statusCallback.getStatusArray()[0];
342             if (st.isConflicted()
343                 || st.getNodeStatus() != Status.Kind.none
344                 || st.getRepositoryNodeStatus() != Status.Kind.none)
345                 fail("File foo.c should return empty status.");
346         }
347         else if (statusCount > 1)
348             fail("File foo.c should not return more than one status.");
349         else
350             fail("File foo.c should return exactly one empty status.");
351     }
352 
353     /**
354      * Test the "out of date" info from {@link
355      * org.apache.subversion.javahl.SVNClient#status()}.
356      *
357      * @throws SubversionException
358      * @throws IOException
359      */
testOODStatus()360     public void testOODStatus() throws SubversionException, IOException
361     {
362         // build the test setup
363         OneTest thisTest = new OneTest();
364 
365         // Make a whole slew of changes to a WC:
366         //
367         //  (root)               r7 - prop change
368         //  iota
369         //  A
370         //  |__mu
371         //  |
372         //  |__B
373         //  |   |__lambda
374         //  |   |
375         //  |   |__E             r12 - deleted
376         //  |   |  |__alpha
377         //  |   |  |__beta
378         //  |   |
379         //  |   |__F             r9 - prop change
380         //  |   |__I             r6 - added dir
381         //  |
382         //  |__C                 r5 - deleted
383         //  |
384         //  |__D
385         //     |__gamma
386         //     |
387         //     |__G
388         //     |  |__pi          r3 - deleted
389         //     |  |__rho         r2 - modify text
390         //     |  |__tau         r4 - modify text
391         //     |
392         //     |__H
393         //        |__chi         r10-11 replaced with file
394         //        |__psi         r13-14 replaced with dir
395         //        |__omega
396         //        |__nu          r8 - added file
397         File file, dir;
398         PrintWriter pw;
399         Status status;
400         MyStatusCallback statusCallback;
401         long rev;             // Resulting rev from co or update
402         long expectedRev = 2;  // Keeps track of the latest rev committed
403 
404         // ----- r2: modify file A/D/G/rho --------------------------
405         file = new File(thisTest.getWorkingCopy(), "A/D/G/rho");
406         pw = new PrintWriter(new FileOutputStream(file, true));
407         pw.print("modification to rho");
408         pw.close();
409         addExpectedCommitItem(thisTest.getWCPath(),
410                               thisTest.getUrl().toString(), "A/D/G/rho",
411                               NodeKind.file, CommitItemStateFlags.TextMods);
412         rev = commit(thisTest, "log msg");
413         assertEquals("wrong revision number from commit", rev, expectedRev++);
414         thisTest.getWc().setItemWorkingCopyRevision("A/D/G/rho", rev);
415         thisTest.getWc().setItemContent("A/D/G/rho",
416             thisTest.getWc().getItemContent("A/D/G/rho")
417             + "modification to rho");
418 
419         statusCallback = new MyStatusCallback();
420         client.status(thisTest.getWCPath() + "/A/D/G/rho", Depth.immediates,
421                       false, true, true, false, false, false,
422                       null, statusCallback);
423         status = statusCallback.getStatusArray()[0];
424         long rhoCommitDate = status.getLastChangedDate().getTime();
425         long rhoCommitRev = rev;
426         String rhoAuthor = status.getLastCommitAuthor();
427 
428         // ----- r3: delete file A/D/G/pi ---------------------------
429         client.remove(thisTest.getWCPathSet("/A/D/G/pi"),
430                       false, false, null, null, null);
431         addExpectedCommitItem(thisTest.getWCPath(), thisTest.getUrl().toString(),
432                               "A/D/G/pi", NodeKind.file,
433                               CommitItemStateFlags.Delete);
434         rev = commit(thisTest, "log msg");
435         assertEquals("wrong revision number from commit", rev, expectedRev++);
436         thisTest.getWc().removeItem("A/D/G/pi");
437 
438         thisTest.getWc().setItemWorkingCopyRevision("A/D/G", rev);
439         assertEquals("wrong revision from update",
440                      update(thisTest, "/A/D/G"), rev);
441         long GCommitRev = rev;
442 
443         // ----- r4: modify file A/D/G/tau --------------------------
444         file = new File(thisTest.getWorkingCopy(), "A/D/G/tau");
445         pw = new PrintWriter(new FileOutputStream(file, true));
446         pw.print("modification to tau");
447         pw.close();
448         addExpectedCommitItem(thisTest.getWCPath(), thisTest.getUrl().toString(),
449                               "A/D/G/tau",NodeKind.file,
450                               CommitItemStateFlags.TextMods);
451         rev = commit(thisTest, "log msg");
452         assertEquals("wrong revision number from commit", rev, expectedRev++);
453         thisTest.getWc().setItemWorkingCopyRevision("A/D/G/tau", rev);
454         thisTest.getWc().setItemContent("A/D/G/tau",
455                 thisTest.getWc().getItemContent("A/D/G/tau")
456                 + "modification to tau");
457         statusCallback = new MyStatusCallback();
458         client.status(thisTest.getWCPath() + "/A/D/G/tau", Depth.immediates,
459                       false, true, true, false, false, false,
460                       null, statusCallback);
461         status = statusCallback.getStatusArray()[0];
462         long tauCommitDate = status.getLastChangedDate().getTime();
463         long tauCommitRev = rev;
464         String tauAuthor = status.getLastCommitAuthor();
465 
466         // ----- r5: delete dir with no children  A/C ---------------
467         client.remove(thisTest.getWCPathSet("/A/C"),
468                       false, false, null, null, null);
469         addExpectedCommitItem(thisTest.getWCPath(), thisTest.getUrl().toString(),
470                               "A/C", NodeKind.dir,
471                               CommitItemStateFlags.Delete);
472         rev = commit(thisTest, "log msg");
473         assertEquals("wrong revision number from commit", rev, expectedRev++);
474         thisTest.getWc().removeItem("A/C");
475         long CCommitRev = rev;
476 
477         // ----- r6: Add dir A/B/I ----------------------------------
478         dir = new File(thisTest.getWorkingCopy(), "A/B/I");
479         dir.mkdir();
480 
481         client.add(dir.getAbsolutePath(), Depth.infinity, false, false, false);
482         addExpectedCommitItem(thisTest.getWCPath(), thisTest.getUrl().toString(),
483                               "A/B/I", NodeKind.dir, CommitItemStateFlags.Add);
484         rev = commit(thisTest, "log msg");
485         assertEquals("wrong revision number from commit", rev, expectedRev++);
486         thisTest.getWc().addItem("A/B/I", null);
487         statusCallback = new MyStatusCallback();
488         client.status(thisTest.getWCPath() + "/A/B/I", Depth.immediates,
489                       false, true, true, false, false, false,
490                       null, statusCallback);
491         status = statusCallback.getStatusArray()[0];
492         long ICommitDate = status.getLastChangedDate().getTime();
493         long ICommitRev = rev;
494         String IAuthor = status.getLastCommitAuthor();
495 
496         // ----- r7: Update then commit prop change on root dir -----
497         thisTest.getWc().setRevision(rev);
498         assertEquals("wrong revision from update",
499                      update(thisTest), rev);
500         thisTest.checkStatus();
501         setprop(thisTest.getWCPath(), "propname", "propval");
502         thisTest.getWc().setItemPropStatus("", Status.Kind.modified);
503         addExpectedCommitItem(thisTest.getWCPath(), thisTest.getUrl().toString(),
504                              null, NodeKind.dir, CommitItemStateFlags.PropMods);
505         rev = commit(thisTest, "log msg");
506         assertEquals("wrong revision number from commit", rev, expectedRev++);
507         thisTest.getWc().setItemWorkingCopyRevision("", rev);
508         thisTest.getWc().setItemPropStatus("", Status.Kind.normal);
509 
510         // ----- r8: Add a file A/D/H/nu ----------------------------
511         file = new File(thisTest.getWorkingCopy(), "A/D/H/nu");
512         pw = new PrintWriter(new FileOutputStream(file));
513         pw.print("This is the file 'nu'.");
514         pw.close();
515         client.add(file.getAbsolutePath(), Depth.empty, false, false, false);
516         addExpectedCommitItem(thisTest.getWCPath(), thisTest.getUrl().toString(),
517                               "A/D/H/nu", NodeKind.file,
518                               CommitItemStateFlags.TextMods +
519                               CommitItemStateFlags.Add);
520         rev = commit(thisTest, "log msg");
521         assertEquals("wrong revision number from commit", rev, expectedRev++);
522         thisTest.getWc().addItem("A/D/H/nu", "This is the file 'nu'.");
523         statusCallback = new MyStatusCallback();
524         client.status(thisTest.getWCPath() + "/A/D/H/nu", Depth.immediates,
525                       false, true, true, false, false, false,
526                       null, statusCallback);
527         status = statusCallback.getStatusArray()[0];
528         long nuCommitDate = status.getLastChangedDate().getTime();
529         long nuCommitRev = rev;
530         String nuAuthor = status.getLastCommitAuthor();
531 
532         // ----- r9: Prop change on A/B/F ---------------------------
533         setprop(thisTest.getWCPath() + "/A/B/F", "propname", "propval");
534         addExpectedCommitItem(thisTest.getWCPath(), thisTest.getUrl().toString(),
535                               "A/B/F", NodeKind.dir,
536                               CommitItemStateFlags.PropMods);
537         rev = commit(thisTest, "log msg");
538         assertEquals("wrong revision number from commit", rev, expectedRev++);
539         thisTest.getWc().setItemPropStatus("A/B/F", Status.Kind.normal);
540         thisTest.getWc().setItemWorkingCopyRevision("A/B/F", rev);
541         statusCallback = new MyStatusCallback();
542         client.status(thisTest.getWCPath() + "/A/B/F", Depth.immediates,
543                       false, true, true, false, false, false,
544                       null, statusCallback);
545         status = statusCallback.getStatusArray()[0];
546         long FCommitDate = status.getLastChangedDate().getTime();
547         long FCommitRev = rev;
548         String FAuthor = status.getLastCommitAuthor();
549 
550         // ----- r10-11: Replace file A/D/H/chi with file -----------
551         client.remove(thisTest.getWCPathSet("/A/D/H/chi"),
552                       false, false, null, null, null);
553         addExpectedCommitItem(thisTest.getWCPath(), thisTest.getUrl().toString(),
554                               "A/D/H/chi", NodeKind.file,
555                               CommitItemStateFlags.Delete);
556         rev = commit(thisTest, "log msg");
557         assertEquals("wrong revision number from commit", rev, expectedRev++);
558         thisTest.getWc().removeItem("A/D/G/pi");
559 
560         file = new File(thisTest.getWorkingCopy(), "A/D/H/chi");
561         pw = new PrintWriter(new FileOutputStream(file));
562         pw.print("This is the replacement file 'chi'.");
563         pw.close();
564         client.add(file.getAbsolutePath(), Depth.empty, false, false, false);
565         addExpectedCommitItem(thisTest.getWCPath(), thisTest.getUrl().toString(),
566                               "A/D/H/chi", NodeKind.file,
567                               CommitItemStateFlags.TextMods +
568                               CommitItemStateFlags.Add);
569         rev = commit(thisTest, "log msg");
570         assertEquals("wrong revision number from commit", rev, expectedRev++);
571         thisTest.getWc().addItem("A/D/H/chi",
572                                  "This is the replacement file 'chi'.");
573         statusCallback = new MyStatusCallback();
574         client.status(thisTest.getWCPath() + "/A/D/H/chi", Depth.immediates,
575                       false, true, true, false, false, false,
576                       null, statusCallback);
577         status = statusCallback.getStatusArray()[0];
578         long chiCommitDate = status.getLastChangedDate().getTime();
579         long chiCommitRev = rev;
580         String chiAuthor = status.getLastCommitAuthor();
581 
582         // ----- r12: Delete dir A/B/E with children ----------------
583         client.remove(thisTest.getWCPathSet("/A/B/E"),
584                       false, false, null, null, null);
585         addExpectedCommitItem(thisTest.getWCPath(), thisTest.getUrl().toString(),
586                               "A/B/E", NodeKind.dir,
587                               CommitItemStateFlags.Delete);
588         rev = commit(thisTest, "log msg");
589         assertEquals("wrong revision number from commit", rev, expectedRev++);
590         thisTest.getWc().removeItem("A/B/E/alpha");
591         thisTest.getWc().removeItem("A/B/E/beta");
592         thisTest.getWc().removeItem("A/B/E");
593 
594         thisTest.getWc().setItemWorkingCopyRevision("A/B", rev);
595         assertEquals("wrong revision from update",
596                      update(thisTest, "/A/B"), rev);
597         Info Binfo = collectInfos(thisTest.getWCPath() + "/A/B", null, null,
598                                    Depth.empty, null)[0];
599         long BCommitDate = Binfo.getLastChangedDate().getTime();
600         long BCommitRev = rev;
601         long ECommitRev = BCommitRev;
602         String BAuthor = Binfo.getLastChangedAuthor();
603 
604         // ----- r13-14: Replace file A/D/H/psi with dir ------------
605         client.remove(thisTest.getWCPathSet("/A/D/H/psi"),
606                       false, false, null, null, null);
607         addExpectedCommitItem(thisTest.getWCPath(), thisTest.getUrl().toString(),
608                               "A/D/H/psi", NodeKind.file,
609                               CommitItemStateFlags.Delete);
610         rev = commit(thisTest, "log msg");
611         assertEquals("wrong revision number from commit", rev, expectedRev++);
612         thisTest.getWc().removeItem("A/D/H/psi");
613         thisTest.getWc().setRevision(rev);
614         assertEquals("wrong revision from update",
615                      update(thisTest), rev);
616         thisTest.getWc().addItem("A/D/H/psi", null);
617         dir = new File(thisTest.getWorkingCopy(), "A/D/H/psi");
618         dir.mkdir();
619         client.add(dir.getAbsolutePath(), Depth.infinity, false, false, false);
620         addExpectedCommitItem(thisTest.getWCPath(), thisTest.getUrl().toString(),
621                               "A/D/H/psi", NodeKind.dir,
622                               CommitItemStateFlags.Add);
623         rev = commit(thisTest, "log msg");
624         assertEquals("wrong revision number from commit", rev, expectedRev++);
625         statusCallback = new MyStatusCallback();
626         client.status(thisTest.getWCPath() + "/A/D/H/psi", Depth.immediates,
627                       false, true, true, false, false, false,
628                       null, statusCallback);
629         status = statusCallback.getStatusArray()[0];
630         long psiCommitDate = status.getLastChangedDate().getTime();
631         long psiCommitRev = rev;
632         String psiAuthor = status.getLastCommitAuthor();
633 
634         // ----- Check status of modfied WC then update it back
635         // -----  to rev 1 so it's out of date
636         thisTest.checkStatus();
637 
638         assertEquals("wrong revision from update",
639                      client.update(thisTest.getWCPathSet(),
640                                    Revision.getInstance(1), Depth.unknown,
641                                    false, false, false, false)[0],
642                      1);
643         thisTest.getWc().setRevision(1);
644 
645         thisTest.getWc().setItemOODInfo("A", psiCommitRev, psiAuthor,
646                                         psiCommitDate, NodeKind.dir);
647 
648         thisTest.getWc().setItemOODInfo("A/B", BCommitRev, BAuthor,
649                                         BCommitDate, NodeKind.dir);
650 
651         thisTest.getWc().addItem("A/B/I", null);
652         thisTest.getWc().setItemOODInfo("A/B/I", ICommitRev, IAuthor,
653                                         ICommitDate, NodeKind.dir);
654         thisTest.getWc().setItemTextStatus("A/B/I", Status.Kind.none);
655         thisTest.getWc().setItemNodeKind("A/B/I", NodeKind.unknown);
656 
657         thisTest.getWc().addItem("A/C", null);
658         thisTest.getWc().setItemReposLastCmtRevision("A/C", CCommitRev);
659         thisTest.getWc().setItemReposKind("A/C", NodeKind.dir);
660 
661         thisTest.getWc().addItem("A/B/E", null);
662         thisTest.getWc().setItemReposLastCmtRevision("A/B/E", ECommitRev);
663         thisTest.getWc().setItemReposKind("A/B/E", NodeKind.dir);
664         thisTest.getWc().addItem("A/B/E/alpha", "This is the file 'alpha'.");
665         thisTest.getWc().addItem("A/B/E/beta", "This is the file 'beta'.");
666 
667         thisTest.getWc().setItemPropStatus("A/B/F", Status.Kind.none);
668         thisTest.getWc().setItemOODInfo("A/B/F", FCommitRev, FAuthor,
669                                         FCommitDate, NodeKind.dir);
670 
671         thisTest.getWc().setItemOODInfo("A/D", psiCommitRev, psiAuthor,
672                                         psiCommitDate, NodeKind.dir);
673 
674         thisTest.getWc().setItemOODInfo("A/D/G", tauCommitRev, tauAuthor,
675                                         tauCommitDate, NodeKind.dir);
676 
677         thisTest.getWc().addItem("A/D/G/pi", "This is the file 'pi'.");
678         thisTest.getWc().setItemReposLastCmtRevision("A/D/G/pi", GCommitRev);
679         thisTest.getWc().setItemReposKind("A/D/G/pi", NodeKind.file);
680 
681         thisTest.getWc().setItemContent("A/D/G/rho",
682                                         "This is the file 'rho'.");
683         thisTest.getWc().setItemOODInfo("A/D/G/rho", rhoCommitRev, rhoAuthor,
684                                         rhoCommitDate, NodeKind.file);
685 
686         thisTest.getWc().setItemContent("A/D/G/tau",
687                                         "This is the file 'tau'.");
688         thisTest.getWc().setItemOODInfo("A/D/G/tau", tauCommitRev, tauAuthor,
689                                         tauCommitDate, NodeKind.file);
690 
691         thisTest.getWc().setItemOODInfo("A/D/H", psiCommitRev, psiAuthor,
692                                         psiCommitDate, NodeKind.dir);
693 
694         thisTest.getWc().setItemWorkingCopyRevision("A/D/H/nu",
695             Revision.SVN_INVALID_REVNUM);
696         thisTest.getWc().setItemTextStatus("A/D/H/nu", Status.Kind.none);
697         thisTest.getWc().setItemNodeKind("A/D/H/nu", NodeKind.unknown);
698         thisTest.getWc().setItemOODInfo("A/D/H/nu", nuCommitRev, nuAuthor,
699                                         nuCommitDate, NodeKind.file);
700 
701         thisTest.getWc().setItemContent("A/D/H/chi",
702                                         "This is the file 'chi'.");
703         thisTest.getWc().setItemOODInfo("A/D/H/chi", chiCommitRev, chiAuthor,
704                                         chiCommitDate, NodeKind.file);
705 
706         thisTest.getWc().removeItem("A/D/H/psi");
707         thisTest.getWc().addItem("A/D/H/psi", "This is the file 'psi'.");
708         // psi was replaced with a directory
709         thisTest.getWc().setItemOODInfo("A/D/H/psi", psiCommitRev, psiAuthor,
710                                         psiCommitDate, NodeKind.dir);
711 
712         thisTest.getWc().setItemPropStatus("", Status.Kind.none);
713         thisTest.getWc().setItemOODInfo("", psiCommitRev, psiAuthor,
714                                         psiCommitDate, NodeKind.dir);
715 
716         thisTest.checkStatus(true);
717     }
718 
719     /**
720      * Test SVNClient.status on externals.
721      * @throws Throwable
722      */
testExternalStatus()723     public void testExternalStatus() throws Throwable
724     {
725         // build the test setup
726         OneTest thisTest = new OneTest();
727 
728 
729         // Add an externals reference to the working copy.
730         client.propertySetLocal(thisTest.getWCPathSet(), "svn:externals",
731                                 "^/A/D/H ADHext".getBytes(),
732                                 Depth.empty, null, false);
733 
734         // Update the working copy to bring in the external subtree.
735         client.update(thisTest.getWCPathSet(), Revision.HEAD,
736                       Depth.unknown, false, false, false, false);
737 
738         // Test status of an external file
739         File psi = new File(thisTest.getWorkingCopy() + "/ADHext", "psi");
740 
741         MyStatusCallback statusCallback = new MyStatusCallback();
742         client.status(fileToSVNPath(psi, false), Depth.unknown,
743                       false, true, true, false, false, false,
744                       null, statusCallback);
745 
746         final int statusCount = statusCallback.getStatusArray().length;
747         if (statusCount == 1)
748         {
749             Status st = statusCallback.getStatusArray()[0];
750             assertFalse(st.isConflicted());
751             assertEquals(Status.Kind.normal, st.getNodeStatus());
752             assertEquals(NodeKind.file, st.getNodeKind());
753         }
754         else if (statusCount > 1)
755             fail("File psi should not return more than one status.");
756         else
757             fail("File psi should return exactly one status.");
758     }
759 
760     /**
761      * Test the basic SVNClient.checkout functionality.
762      * @throws Throwable
763      */
testBasicCheckout()764     public void testBasicCheckout() throws Throwable
765     {
766         // build the test setup
767         OneTest thisTest = new OneTest();
768         try
769         {
770             // obstructed checkout must fail
771             client.checkout(thisTest.getUrl() + "/A", thisTest.getWCPath(),
772                             null, null, Depth.infinity, false, false);
773             fail("missing exception");
774         }
775         catch (ClientException expected)
776         {
777         }
778         // modify file A/mu
779         File mu = new File(thisTest.getWorkingCopy(), "A/mu");
780         PrintWriter muWriter = new PrintWriter(new FileOutputStream(mu, true));
781         muWriter.print("appended mu text");
782         muWriter.close();
783         thisTest.getWc().setItemTextStatus("A/mu", Status.Kind.modified);
784 
785         // delete A/B/lambda without svn
786         File lambda = new File(thisTest.getWorkingCopy(), "A/B/lambda");
787         lambda.delete();
788         thisTest.getWc().setItemTextStatus("A/B/lambda", Status.Kind.missing);
789 
790         // remove A/D/G
791         client.remove(thisTest.getWCPathSet("/A/D/G"),
792                       false, false, null, null, null);
793         thisTest.getWc().setItemTextStatus("A/D/G", Status.Kind.deleted);
794         thisTest.getWc().setItemTextStatus("A/D/G/pi", Status.Kind.deleted);
795         thisTest.getWc().setItemTextStatus("A/D/G/rho", Status.Kind.deleted);
796         thisTest.getWc().setItemTextStatus("A/D/G/tau", Status.Kind.deleted);
797 
798         // check the status of the working copy
799         thisTest.checkStatus();
800 
801         // recheckout the working copy
802         client.checkout(thisTest.getUrl().toString(), thisTest.getWCPath(),
803                    null, null, Depth.infinity, false, false);
804 
805         // deleted file should reapear
806         thisTest.getWc().setItemTextStatus("A/B/lambda", Status.Kind.normal);
807 
808         // check the status of the working copy
809         thisTest.checkStatus();
810     }
811 
812     /**
813      * Test the basic SVNClient.commit functionality.
814      * @throws Throwable
815      */
testBasicCommit()816     public void testBasicCommit() throws Throwable
817     {
818         // build the test setup
819         OneTest thisTest = new OneTest();
820 
821         // modify file A/mu
822         File mu = new File(thisTest.getWorkingCopy(), "A/mu");
823         PrintWriter muWriter = new PrintWriter(new FileOutputStream(mu, true));
824         muWriter.print("appended mu text");
825         muWriter.close();
826         thisTest.getWc().setItemWorkingCopyRevision("A/mu", 2);
827         thisTest.getWc().setItemContent("A/mu",
828                 thisTest.getWc().getItemContent("A/mu") + "appended mu text");
829         addExpectedCommitItem(thisTest.getWCPath(),
830                 thisTest.getUrl().toString(), "A/mu",NodeKind.file,
831                 CommitItemStateFlags.TextMods);
832 
833         // modify file A/D/G/rho
834         File rho = new File(thisTest.getWorkingCopy(), "A/D/G/rho");
835         PrintWriter rhoWriter =
836             new PrintWriter(new FileOutputStream(rho, true));
837         rhoWriter.print("new appended text for rho");
838         rhoWriter.close();
839         thisTest.getWc().setItemWorkingCopyRevision("A/D/G/rho", 2);
840         thisTest.getWc().setItemContent("A/D/G/rho",
841                 thisTest.getWc().getItemContent("A/D/G/rho")
842                 + "new appended text for rho");
843         addExpectedCommitItem(thisTest.getWCPath(),
844                 thisTest.getUrl().toString(), "A/D/G/rho",NodeKind.file,
845                 CommitItemStateFlags.TextMods);
846 
847         // commit the changes
848         checkCommitRevision(thisTest, "wrong revision number from commit", 2,
849                             thisTest.getWCPathSet(), "log msg", Depth.infinity,
850                             false, false, null, null);
851 
852         // check the status of the working copy
853         thisTest.checkStatus();
854     }
855 
856     /**
857      * Test the basic property setting/getting functionality.
858      * @throws Throwable
859      */
testBasicProperties()860     public void testBasicProperties() throws Throwable
861     {
862         OneTest thisTest = new OneTest();
863         WC wc = thisTest.getWc();
864 
865         // Check getting properties the non-callback way
866         String itemPath = fileToSVNPath(new File(thisTest.getWCPath(),
867                                                  "iota"),
868                                         false);
869 
870         byte[] BINARY_DATA = {-12, -125, -51, 43, 5, 47, 116, -72, -120,
871                 2, -98, -100, -73, 61, 118, 74, 36, 38, 56, 107, 45, 91, 38, 107, -87,
872                 119, -107, -114, -45, -128, -69, 96};
873         setprop(itemPath, "abc", BINARY_DATA);
874         Map<String, byte[]> properties = collectProperties(itemPath, null,
875                                                     null, Depth.empty, null);
876 
877         assertTrue(Arrays.equals(BINARY_DATA, properties.get("abc")));
878 
879         wc.setItemPropStatus("iota", Status.Kind.modified);
880         thisTest.checkStatus();
881 
882         // Check getting properties the callback way
883         itemPath = fileToSVNPath(new File(thisTest.getWCPath(),
884                                           "/A/B/E/alpha"),
885                                  false);
886         String alphaVal = "qrz";
887         setprop(itemPath, "cqcq", alphaVal.getBytes());
888 
889         final Map<String, Map<String, byte[]>> propMaps =
890                                     new HashMap<String, Map<String, byte[]>>();
891         client.properties(itemPath, null, null, Depth.empty, null,
892             new ProplistCallback () {
893                 public void singlePath(String path, Map<String, byte[]> props)
894                 { propMaps.put(path, props); }
895             });
896         Map<String, byte[]> propMap = propMaps.get(itemPath);
897         for (String key : propMap.keySet())
898         {
899             assertEquals("cqcq", key);
900             assertEquals(alphaVal, new String(propMap.get(key)));
901         }
902 
903         wc.setItemPropStatus("A/B/E/alpha", Status.Kind.modified);
904         thisTest.checkStatus();
905     }
906 
907     /**
908      * Test property inheritance.
909      * @throws Throwable
910      */
testInheritedProperties()911     public void testInheritedProperties() throws Throwable
912     {
913         OneTest thisTest = new OneTest();
914         WC wc = thisTest.getWc();
915 
916         String adirPath = fileToSVNPath(new File(thisTest.getWCPath(),
917                                                  "/A"),
918                                         false);
919         String alphaPath = fileToSVNPath(new File(thisTest.getWCPath(),
920                                                   "/A/B/E/alpha"),
921                                          false);
922 
923         String propval = "ybg";
924         setprop(adirPath, "ahqrtz", propval.getBytes());
925 
926         final Map<String, Collection<InheritedProplistCallback.InheritedItem>> ipropMaps =
927             new HashMap<String, Collection<InheritedProplistCallback.InheritedItem>>();
928 
929         client.properties(alphaPath, null, null, Depth.empty, null,
930             new InheritedProplistCallback () {
931                 public void singlePath(
932                     String path, Map<String, byte[]> props,
933                     Collection<InheritedProplistCallback.InheritedItem> iprops)
934                 { ipropMaps.put(path, iprops); }
935             });
936         Collection<InheritedProplistCallback.InheritedItem> iprops = ipropMaps.get(alphaPath);
937         for (InheritedProplistCallback.InheritedItem item : iprops)
938         {
939             for (String key : item.properties.keySet())
940             {
941                 assertEquals("ahqrtz", key);
942                 assertEquals(propval, new String(item.properties.get(key)));
943             }
944         }
945 
946         wc.setItemPropStatus("A", Status.Kind.modified);
947         thisTest.checkStatus();
948     }
949 
950     /**
951      * Test the basic SVNClient.update functionality.
952      * @throws Throwable
953      */
testBasicUpdate()954     public void testBasicUpdate() throws Throwable
955     {
956         // build the test setup. Used for the changes
957         OneTest thisTest = new OneTest();
958 
959         // build the backup test setup. That is the one that will be updated
960         OneTest backupTest = thisTest.copy(".backup");
961 
962         // modify A/mu
963         File mu = new File(thisTest.getWorkingCopy(), "A/mu");
964         PrintWriter muWriter = new PrintWriter(new FileOutputStream(mu, true));
965         muWriter.print("appended mu text");
966         muWriter.close();
967         thisTest.getWc().setItemWorkingCopyRevision("A/mu", 2);
968         thisTest.getWc().setItemContent("A/mu",
969                 thisTest.getWc().getItemContent("A/mu") + "appended mu text");
970         addExpectedCommitItem(thisTest.getWCPath(),
971                 thisTest.getUrl().toString(), "A/mu",NodeKind.file,
972                 CommitItemStateFlags.TextMods);
973 
974         // modify A/D/G/rho
975         File rho = new File(thisTest.getWorkingCopy(), "A/D/G/rho");
976         PrintWriter rhoWriter =
977             new PrintWriter(new FileOutputStream(rho, true));
978         rhoWriter.print("new appended text for rho");
979         rhoWriter.close();
980         thisTest.getWc().setItemWorkingCopyRevision("A/D/G/rho", 2);
981         thisTest.getWc().setItemContent("A/D/G/rho",
982                 thisTest.getWc().getItemContent("A/D/G/rho")
983                 + "new appended text for rho");
984         addExpectedCommitItem(thisTest.getWCPath(),
985                 thisTest.getUrl().toString(), "A/D/G/rho",NodeKind.file,
986                 CommitItemStateFlags.TextMods);
987 
988         // commit the changes
989         checkCommitRevision(thisTest, "wrong revision number from commit", 2,
990                             thisTest.getWCPathSet(), "log msg", Depth.infinity,
991                             false, false, null, null);
992 
993         // check the status of the working copy
994         thisTest.checkStatus();
995 
996         // update the backup test
997         assertEquals("wrong revision number from update",
998                      update(backupTest), 2);
999 
1000         // set the expected working copy layout for the backup test
1001         backupTest.getWc().setItemWorkingCopyRevision("A/mu", 2);
1002         backupTest.getWc().setItemContent("A/mu",
1003                 backupTest.getWc().getItemContent("A/mu") + "appended mu text");
1004         backupTest.getWc().setItemWorkingCopyRevision("A/D/G/rho", 2);
1005         backupTest.getWc().setItemContent("A/D/G/rho",
1006                 backupTest.getWc().getItemContent("A/D/G/rho")
1007                 + "new appended text for rho");
1008 
1009         // check the status of the working copy of the backup test
1010         backupTest.checkStatus();
1011     }
1012 
1013     /**
1014      * Test basic SVNClient.mkdir with URL parameter functionality.
1015      * @throws Throwable
1016      */
testBasicMkdirUrl()1017     public void testBasicMkdirUrl() throws Throwable
1018     {
1019         // build the test setup.
1020         OneTest thisTest = new OneTest();
1021 
1022         // create Y and Y/Z directories in the repository
1023         addExpectedCommitItem(null, thisTest.getUrl().toString(), "Y", NodeKind.dir,
1024                               CommitItemStateFlags.Add);
1025         addExpectedCommitItem(null, thisTest.getUrl().toString(), "Y/Z", NodeKind.dir,
1026                               CommitItemStateFlags.Add);
1027         Set<String> urls = new HashSet<String>(2);
1028         urls.add(thisTest.getUrl() + "/Y");
1029         urls.add(thisTest.getUrl() + "/Y/Z");
1030         client.mkdir(urls, false, null, new ConstMsg("log_msg"), null);
1031 
1032         // add the new directories the expected working copy layout
1033         thisTest.getWc().addItem("Y", null);
1034         thisTest.getWc().setItemWorkingCopyRevision("Y", 2);
1035         thisTest.getWc().addItem("Y/Z", null);
1036         thisTest.getWc().setItemWorkingCopyRevision("Y/Z", 2);
1037 
1038         // update the working copy
1039         assertEquals("wrong revision from update",
1040                      update(thisTest), 2);
1041 
1042         // check the status of the working copy
1043         thisTest.checkStatus();
1044     }
1045 
1046     /**
1047      * Test the {@link SVNClientInterface.copy()} API.
1048      * @since 1.5
1049      */
testCopy()1050     public void testCopy()
1051         throws SubversionException, IOException
1052     {
1053         OneTest thisTest = new OneTest();
1054 
1055         WC wc = thisTest.getWc();
1056         final Revision firstRevision = Revision.getInstance(1);
1057         final Revision pegRevision = null;  // Defaults to Revision.HEAD.
1058 
1059         // Copy files from A/B/E to A/B/F.
1060         String[] srcPaths = { "alpha", "beta" };
1061         List<CopySource> sources = new ArrayList<CopySource>(srcPaths.length);
1062         for (String fileName : srcPaths)
1063         {
1064             sources.add(
1065                 new CopySource(new File(thisTest.getWorkingCopy(),
1066                                         "A/B/E/" + fileName).getPath(),
1067                                firstRevision, pegRevision));
1068             wc.addItem("A/B/F/" + fileName,
1069                        wc.getItemContent("A/B/E/" + fileName));
1070             wc.setItemWorkingCopyRevision("A/B/F/" + fileName, 2);
1071             addExpectedCommitItem(thisTest.getWCPath(),
1072                                  thisTest.getUrl().toString(),
1073                                   "A/B/F/" + fileName, NodeKind.file,
1074                                   CommitItemStateFlags.Add |
1075                                   CommitItemStateFlags.IsCopy);
1076         }
1077         client.copy(sources,
1078                     new File(thisTest.getWorkingCopy(), "A/B/F").getPath(),
1079                     true, false, false, false, false, null, null, null, null);
1080 
1081         // Commit the changes, and check the state of the WC.
1082         checkCommitRevision(thisTest,
1083                             "Unexpected WC revision number after commit", 2,
1084                             thisTest.getWCPathSet(), "Copy files",
1085                             Depth.infinity, false, false, null, null);
1086         thisTest.checkStatus();
1087 
1088         assertExpectedSuggestion(thisTest.getUrl() + "/A/B/E/alpha", "A/B/F/alpha", thisTest);
1089 
1090         // Now test a WC to URL copy
1091         List<CopySource> wcSource = new ArrayList<CopySource>(1);
1092         wcSource.add(new CopySource(new File(thisTest.getWorkingCopy(),
1093                                         "A/B").getPath(), Revision.WORKING,
1094                                     Revision.WORKING));
1095         client.copy(wcSource, thisTest.getUrl() + "/parent/A/B",
1096                     true, true, false, false, false, null, null,
1097                     new ConstMsg("Copy WC to URL"), null);
1098 
1099         // update the WC to get new folder and confirm the copy
1100         assertEquals("wrong revision number from update",
1101                      update(thisTest), 3);
1102     }
1103 
1104 
1105     // Set up externals references in the working copy for the
1106     // pin-externals tests.
setupPinExternalsTest(OneTest thisTest)1107     private void setupPinExternalsTest(OneTest thisTest) throws Throwable
1108     {
1109         byte[] extref = ("^/A/D/H ADHext\n" +
1110                          "^/A/D/H ADHext2\n" +
1111                          "^/A/D/H@1 peggedADHext\n" +
1112                          "-r1 ^/A/D/H revvedADHext\n").getBytes();
1113         Set<String> paths = new HashSet<String>();
1114         paths.add(thisTest.getWCPath() + "/A/B");
1115 
1116         // Add an externals reference to the working copy.
1117         client.propertySetLocal(paths, "svn:externals", extref,
1118                                 Depth.empty, null, false);
1119 
1120         // Commit the externals definition
1121         client.commit(thisTest.getWCPathSet(), Depth.infinity,
1122                       false, false, null, null,
1123                       new ConstMsg("Set svn:externals"), null);
1124 
1125         // Update the working copy to bring in the external subtree.
1126         client.update(thisTest.getWCPathSet(), Revision.HEAD,
1127                       Depth.unknown, false, false, false, false);
1128     }
1129 
1130     /**
1131      * Test WC-to-WC copy with implicit pinned externals
1132      * @throws Throwable
1133      */
testCopyPinExternals_wc2wc()1134     public void testCopyPinExternals_wc2wc() throws Throwable
1135     {
1136         // build the test setup
1137         OneTest thisTest = new OneTest();
1138         setupPinExternalsTest(thisTest);
1139 
1140         List<CopySource> sources = new ArrayList<CopySource>(1);
1141         sources.add(new CopySource(thisTest.getWCPath() + "/A/B", null, null));
1142         String target = thisTest.getWCPath() + "/A/Bcopy";
1143         client.copy(sources, target, true, false, false, false,
1144                     true,       // pinExternals
1145                     null,       // externalsToPin
1146                     null, null, null);
1147 
1148         // Verification
1149         String expected = ("^/A/D/H@2 ADHext\n" +
1150                            "^/A/D/H@2 ADHext2\n" +
1151                            "^/A/D/H@1 peggedADHext\n" +
1152                            "-r1 ^/A/D/H@2 revvedADHext\n");
1153         String actual =
1154             new String(client.propertyGet(target, "svn:externals", null, null));
1155 
1156         assertEquals(expected, actual);
1157     }
1158 
1159     /**
1160      * Test WC-to-REPO copy with implicit pinned externals
1161      * @throws Throwable
1162      */
testCopyPinExternals_wc2repo()1163     public void testCopyPinExternals_wc2repo() throws Throwable
1164     {
1165         // build the test setup
1166         OneTest thisTest = new OneTest();
1167         setupPinExternalsTest(thisTest);
1168 
1169         List<CopySource> sources = new ArrayList<CopySource>(1);
1170         sources.add(new CopySource(thisTest.getWCPath() + "/A/B", null, null));
1171         String target = thisTest.getUrl() + "/A/Bcopy";
1172         client.copy(sources, target, true, false, false, false,
1173                     true,       // pinExternals
1174                     null,       // externalsToPin
1175                     null, new ConstMsg("Copy WC to REPO"), null);
1176 
1177         // Verification
1178         String expected = ("^/A/D/H@2 ADHext\n" +
1179                            "^/A/D/H@2 ADHext2\n" +
1180                            "^/A/D/H@1 peggedADHext\n" +
1181                            "-r1 ^/A/D/H@2 revvedADHext\n");
1182         String actual =
1183             new String(client.propertyGet(target, "svn:externals", null, null));
1184 
1185         assertEquals(expected, actual);
1186     }
1187 
1188     /**
1189      * Test REPO-to-WC copy with implicit pinned externals
1190      * @throws Throwable
1191      */
testCopyPinExternals_repo2wc()1192     public void testCopyPinExternals_repo2wc() throws Throwable
1193     {
1194         // build the test setup
1195         OneTest thisTest = new OneTest();
1196         setupPinExternalsTest(thisTest);
1197 
1198         List<CopySource> sources = new ArrayList<CopySource>(1);
1199         sources.add(new CopySource(thisTest.getUrl() + "/A/B", null, null));
1200         String target = thisTest.getWCPath() + "/A/Bcopy";
1201         client.copy(sources, target, true, false, false, false,
1202                     true,       // pinExternals
1203                     null,       // externalsToPin
1204                     null, null, null);
1205 
1206         // Verification
1207         String expected = ("^/A/D/H@2 ADHext\n" +
1208                            "^/A/D/H@2 ADHext2\n" +
1209                            "^/A/D/H@1 peggedADHext\n" +
1210                            "-r1 ^/A/D/H@2 revvedADHext\n");
1211         String actual =
1212             new String(client.propertyGet(target, "svn:externals", null, null));
1213 
1214         assertEquals(expected, actual);
1215     }
1216 
1217     /**
1218      * Test REPO-to-REPO copy with implicit pinned externals
1219      * @throws Throwable
1220      */
testCopyPinExternals_repo2repo()1221     public void testCopyPinExternals_repo2repo() throws Throwable
1222     {
1223         // build the test setup
1224         OneTest thisTest = new OneTest();
1225         setupPinExternalsTest(thisTest);
1226 
1227         List<CopySource> sources = new ArrayList<CopySource>(1);
1228         sources.add(new CopySource(thisTest.getUrl() + "/A/B", null, null));
1229         String target = thisTest.getUrl() + "/A/Bcopy";
1230         client.copy(sources, target, true, false, false, false,
1231                     true,       // pinExternals
1232                     null,       // externalsToPin
1233                     null, new ConstMsg("Copy WC to REPO"), null);
1234 
1235         // Verification
1236         String expected = ("^/A/D/H@2 ADHext\n" +
1237                            "^/A/D/H@2 ADHext2\n" +
1238                            "^/A/D/H@1 peggedADHext\n" +
1239                            "-r1 ^/A/D/H@2 revvedADHext\n");
1240         String actual =
1241             new String(client.propertyGet(target, "svn:externals", null, null));
1242 
1243         assertEquals(expected, actual);
1244     }
1245 
1246     /**
1247      * Test REPO-to-REPO copy with eplicit pinned externals
1248      * @throws Throwable
1249      */
testCopyPinExternals_repo2repo_explicit()1250     public void testCopyPinExternals_repo2repo_explicit() throws Throwable
1251     {
1252         // build the test setup
1253         OneTest thisTest = new OneTest();
1254         setupPinExternalsTest(thisTest);
1255 
1256         String sourceUrl = thisTest.getUrl() + "/A/B";
1257         Map<String, List<ExternalItem>> externalsToPin =
1258             new HashMap<String, List<ExternalItem>>();
1259         List<ExternalItem> items = new ArrayList<ExternalItem>(1);
1260         items.add(new ExternalItem("ADHext", "^/A/D/H", null, null));
1261         externalsToPin.put(sourceUrl, items);
1262 
1263         List<CopySource> sources = new ArrayList<CopySource>(1);
1264         sources.add(new CopySource(sourceUrl, null, null));
1265         String target = thisTest.getUrl() + "/A/Bcopy";
1266         client.copy(sources, target, true, false, false, false,
1267                     true,       // pinExternals
1268                     externalsToPin,
1269                     null, new ConstMsg("Copy WC to REPO"), null);
1270 
1271         // Verification
1272         String expected = ("^/A/D/H@2 ADHext\n" +
1273                            "^/A/D/H ADHext2\n" +
1274                            "^/A/D/H@1 peggedADHext\n" +
1275                            "-r1 ^/A/D/H revvedADHext\n");
1276         String actual =
1277             new String(client.propertyGet(target, "svn:externals", null, null));
1278 
1279         assertEquals(expected, actual);
1280     }
1281 
1282     /**
1283      * Test REPO-to-REPO copy with explicit pinned externals that
1284      * don't correspond to actual externals
1285      * @throws Throwable
1286      */
testCopyPinExternals_repo2repo_corkscrew()1287     public void testCopyPinExternals_repo2repo_corkscrew() throws Throwable
1288     {
1289         // build the test setup
1290         OneTest thisTest = new OneTest();
1291         setupPinExternalsTest(thisTest);
1292 
1293         String sourceUrl = thisTest.getUrl() + "/A/B";
1294         Map<String, List<ExternalItem>> externalsToPin =
1295             new HashMap<String, List<ExternalItem>>();
1296         List<ExternalItem> items = new ArrayList<ExternalItem>(1);
1297         items.add(new ExternalItem("ADHext", "^/A/D/H", null, null));
1298         externalsToPin.put(sourceUrl + "/A", items);
1299 
1300         List<CopySource> sources = new ArrayList<CopySource>(1);
1301         sources.add(new CopySource(sourceUrl, null, null));
1302         String target = thisTest.getUrl() + "/A/Bcopy";
1303         client.copy(sources, target, true, false, false, false,
1304                     true,       // pinExternals
1305                     externalsToPin,
1306                     null, new ConstMsg("Copy WC to REPO"), null);
1307 
1308         // Verification
1309         String expected = ("^/A/D/H ADHext\n" +
1310                            "^/A/D/H ADHext2\n" +
1311                            "^/A/D/H@1 peggedADHext\n" +
1312                            "-r1 ^/A/D/H revvedADHext\n");
1313         String actual =
1314             new String(client.propertyGet(target, "svn:externals", null, null));
1315 
1316         assertEquals(expected, actual);
1317     }
1318 
1319     /**
1320      * Test the {@link SVNClientInterface.move()} API.
1321      * @since 1.5
1322      */
testMove()1323     public void testMove()
1324         throws SubversionException, IOException
1325     {
1326         OneTest thisTest = new OneTest();
1327         WC wc = thisTest.getWc();
1328 
1329         // Move files from A/B/E to A/B/F.
1330         Set<String> relPaths = new HashSet<String>(2);
1331         relPaths.add("alpha");
1332         relPaths.add("beta");
1333         Set<String> srcPaths = new HashSet<String>(2);
1334         for (String fileName : relPaths)
1335         {
1336             srcPaths.add(new File(thisTest.getWorkingCopy(),
1337                                   "A/B/E/" + fileName).getPath());
1338 
1339             wc.addItem("A/B/F/" + fileName,
1340                        wc.getItemContent("A/B/E/" + fileName));
1341             wc.setItemWorkingCopyRevision("A/B/F/" + fileName, 2);
1342             addExpectedCommitItem(thisTest.getWCPath(), thisTest.getUrl().toString(),
1343                                   "A/B/F/" + fileName, NodeKind.file,
1344                                   CommitItemStateFlags.Add |
1345                                   CommitItemStateFlags.IsCopy);
1346 
1347             wc.removeItem("A/B/E/" + fileName);
1348             addExpectedCommitItem(thisTest.getWCPath(), thisTest.getUrl().toString(),
1349                                   "A/B/E/" + fileName, NodeKind.file,
1350                                   CommitItemStateFlags.Delete);
1351         }
1352         client.move(srcPaths,
1353                     new File(thisTest.getWorkingCopy(), "A/B/F").getPath(),
1354                     false, true, false, false, false, null, null, null);
1355 
1356         MyStatusCallback statusCallback = new MyStatusCallback();
1357         String statusPath = fileToSVNPath(new File(thisTest.getWCPath() + "/A/B"), true);
1358         client.status(statusPath, Depth.infinity,
1359                       false, true, false, false, true, false,
1360                       null, statusCallback);
1361         Status[] statusList = statusCallback.getStatusArray();
1362         assertEquals(statusPath + "/F/alpha",
1363                      statusList[0].getMovedToAbspath());
1364         assertEquals(statusPath + "/F/beta",
1365                      statusList[1].getMovedToAbspath());
1366         assertEquals(statusPath + "/E/alpha",
1367                      statusList[2].getMovedFromAbspath());
1368         assertEquals(statusPath + "/E/beta",
1369                      statusList[3].getMovedFromAbspath());
1370 
1371         // Commit the changes, and check the state of the WC.
1372         checkCommitRevision(thisTest,
1373                             "Unexpected WC revision number after commit", 2,
1374                             thisTest.getWCPathSet(), "Move files",
1375                             Depth.infinity, false, false, null, null);
1376         thisTest.checkStatus();
1377 
1378         assertExpectedSuggestion(thisTest.getUrl() + "/A/B/E/alpha", "A/B/F/alpha", thisTest);
1379     }
1380 
1381     /**
1382      * Check that half a move cannot be committed.
1383      * @since 1.9
1384      */
testCommitPartialMove()1385     public void testCommitPartialMove() throws Throwable
1386     {
1387         OneTest thisTest = new OneTest();
1388         String root = thisTest.getWorkingCopy().getAbsolutePath();
1389         ClientException caught = null;
1390 
1391         Set<String> srcPaths = new HashSet<String>(1);
1392         srcPaths.add(root + "/A/B/E/alpha");
1393         client.move(srcPaths, root + "/moved-alpha",
1394                     false, false, false, false, false, null, null, null);
1395 
1396         try {
1397             client.commit(srcPaths, Depth.infinity, false, false, null, null,
1398                           new ConstMsg("Commit half of a move"), null);
1399         } catch (ClientException ex) {
1400             caught = ex;
1401         }
1402 
1403         assertNotNull("Commit of partial move did not fail", caught);
1404 
1405         List<ClientException.ErrorMessage> msgs = caught.getAllMessages();
1406         assertTrue(msgs.size() >= 3);
1407         assertTrue(msgs.get(0).getMessage().startsWith("Illegal target"));
1408         assertTrue(msgs.get(1).getMessage().startsWith("Commit failed"));
1409         assertTrue(msgs.get(2).getMessage().startsWith("Cannot commit"));
1410     }
1411 
1412     /**
1413      * Assert that the first merge source suggested for
1414      * <code>destPath</code> at {@link Revision#WORKING} and {@link
1415      * Revision#HEAD} is equivalent to <code>expectedSrc</code>.
1416      * @exception SubversionException If retrieval of the copy source fails.
1417      * @since 1.5
1418      */
assertExpectedSuggestion(String expectedSrc, String destPath, OneTest thisTest)1419     private void assertExpectedSuggestion(String expectedSrc,
1420                                           String destPath, OneTest thisTest)
1421         throws SubversionException
1422     {
1423         String wcPath = fileToSVNPath(new File(thisTest.getWCPath(),
1424                                                destPath), false);
1425         Set<String> suggestions = client.suggestMergeSources(wcPath,
1426                                                              Revision.WORKING);
1427         assertNotNull(suggestions);
1428         assertTrue(suggestions.size() >= 1);
1429         assertTrue("Copy source path not found in suggestions: " +
1430                    expectedSrc,
1431                    suggestions.contains(expectedSrc));
1432 
1433         // Same test using URL
1434         String url = thisTest.getUrl() + "/" + destPath;
1435         suggestions = client.suggestMergeSources(url, Revision.HEAD);
1436         assertNotNull(suggestions);
1437         assertTrue(suggestions.size() >= 1);
1438         assertTrue("Copy source path not found in suggestions: " +
1439                    expectedSrc,
1440                    suggestions.contains(expectedSrc));
1441 
1442     }
1443 
1444     /**
1445      * Tests that the passed start and end revision are contained
1446      * within the array of revisions.
1447      * @since 1.5
1448      */
assertExpectedMergeRange(long start, long end, long[] revisions)1449     private void assertExpectedMergeRange(long start, long end,
1450                                           long[] revisions)
1451     {
1452         Arrays.sort(revisions);
1453         for (int i = 0; i < revisions.length; i++) {
1454             if (revisions[i] <= start) {
1455                 for (int j = i; j < revisions.length; j++)
1456                 {
1457                     if (end <= revisions[j])
1458                         return;
1459                 }
1460                 fail("End revision: " + end + " was not in range: " + revisions[0] +
1461                         " : " + revisions[revisions.length - 1]);
1462                 return;
1463             }
1464         }
1465         fail("Start revision: " + start + " was not in range: " + revisions[0] +
1466                 " : " + revisions[revisions.length - 1]);
1467     }
1468 
1469     /**
1470      * Test the basic SVNClient.update functionality with concurrent
1471      * changes in the repository and the working copy.
1472      * @throws Throwable
1473      */
testBasicMergingUpdate()1474     public void testBasicMergingUpdate() throws Throwable
1475     {
1476         // build the first working copy
1477         OneTest thisTest = new OneTest();
1478 
1479         // append 10 lines to A/mu
1480         File mu = new File(thisTest.getWorkingCopy(), "A/mu");
1481         PrintWriter muWriter = new PrintWriter(new FileOutputStream(mu, true));
1482         String muContent = thisTest.getWc().getItemContent("A/mu");
1483         for (int i = 2; i < 11; i++)
1484         {
1485             muWriter.print("\nThis is line " + i + " in mu");
1486             muContent = muContent + "\nThis is line " + i + " in mu";
1487         }
1488         muWriter.close();
1489         thisTest.getWc().setItemWorkingCopyRevision("A/mu", 2);
1490         thisTest.getWc().setItemContent("A/mu", muContent);
1491         addExpectedCommitItem(thisTest.getWorkingCopy().getAbsolutePath(),
1492                               thisTest.getUrl().toString(), "A/mu", NodeKind.file,
1493                               CommitItemStateFlags.TextMods);
1494 
1495         // append 10 line to A/D/G/rho
1496         File rho = new File(thisTest.getWorkingCopy(), "A/D/G/rho");
1497         PrintWriter rhoWriter =
1498             new PrintWriter(new FileOutputStream(rho, true));
1499         String rhoContent = thisTest.getWc().getItemContent("A/D/G/rho");
1500         for (int i = 2; i < 11; i++)
1501         {
1502             rhoWriter.print("\nThis is line " + i + " in rho");
1503             rhoContent = rhoContent + "\nThis is line " + i + " in rho";
1504         }
1505         rhoWriter.close();
1506         thisTest.getWc().setItemWorkingCopyRevision("A/D/G/rho", 2);
1507         thisTest.getWc().setItemContent("A/D/G/rho", rhoContent);
1508         addExpectedCommitItem(thisTest.getWCPath(),
1509                               thisTest.getUrl().toString(), "A/D/G/rho",
1510                               NodeKind.file, CommitItemStateFlags.TextMods);
1511 
1512         // commit the changes
1513         checkCommitRevision(thisTest, "wrong revision number from commit", 2,
1514                             thisTest.getWCPathSet(), "log msg", Depth.infinity,
1515                             false, false, null, null);
1516 
1517         // check the status of the first working copy
1518         thisTest.checkStatus();
1519 
1520         // create a backup copy of the working copy
1521         OneTest backupTest = thisTest.copy(".backup");
1522 
1523         // change the last line of A/mu in the first working copy
1524         muWriter = new PrintWriter(new FileOutputStream(mu, true));
1525         muContent = thisTest.getWc().getItemContent("A/mu");
1526         muWriter.print(" Appended to line 10 of mu");
1527         muContent = muContent + " Appended to line 10 of mu";
1528         muWriter.close();
1529         thisTest.getWc().setItemWorkingCopyRevision("A/mu", 3);
1530         thisTest.getWc().setItemContent("A/mu", muContent);
1531         addExpectedCommitItem(thisTest.getWCPath(),
1532                               thisTest.getUrl().toString(), "A/mu", NodeKind.file,
1533                               CommitItemStateFlags.TextMods);
1534 
1535         // change the last line of A/mu in the first working copy
1536         rhoWriter = new PrintWriter(new FileOutputStream(rho, true));
1537         rhoContent = thisTest.getWc().getItemContent("A/D/G/rho");
1538         rhoWriter.print(" Appended to line 10 of rho");
1539         rhoContent = rhoContent + " Appended to line 10 of rho";
1540         rhoWriter.close();
1541         thisTest.getWc().setItemWorkingCopyRevision("A/D/G/rho", 3);
1542         thisTest.getWc().setItemContent("A/D/G/rho", rhoContent);
1543         addExpectedCommitItem(thisTest.getWCPath(),
1544                               thisTest.getUrl().toString(), "A/D/G/rho",
1545                               NodeKind.file,
1546                               CommitItemStateFlags.TextMods);
1547 
1548         // commit these changes to the repository
1549         checkCommitRevision(thisTest, "wrong revision number from commit", 3,
1550                             thisTest.getWCPathSet(), "log msg", Depth.infinity,
1551                             false, false, null, null);
1552 
1553         // check the status of the first working copy
1554         thisTest.checkStatus();
1555 
1556         // modify the first line of A/mu in the backup working copy
1557         mu = new File(backupTest.getWorkingCopy(), "A/mu");
1558         muWriter = new PrintWriter(new FileOutputStream(mu));
1559         muWriter.print("This is the new line 1 in the backup copy of mu");
1560         muContent = "This is the new line 1 in the backup copy of mu";
1561         for (int i = 2; i < 11; i++)
1562         {
1563             muWriter.print("\nThis is line " + i + " in mu");
1564             muContent = muContent + "\nThis is line " + i + " in mu";
1565         }
1566         muWriter.close();
1567         backupTest.getWc().setItemWorkingCopyRevision("A/mu", 3);
1568         muContent = muContent + " Appended to line 10 of mu";
1569         backupTest.getWc().setItemContent("A/mu", muContent);
1570         backupTest.getWc().setItemTextStatus("A/mu", Status.Kind.modified);
1571 
1572         // modify the first line of A/D/G/rho in the backup working copy
1573         rho = new File(backupTest.getWorkingCopy(), "A/D/G/rho");
1574         rhoWriter = new PrintWriter(new FileOutputStream(rho));
1575         rhoWriter.print("This is the new line 1 in the backup copy of rho");
1576         rhoContent = "This is the new line 1 in the backup copy of rho";
1577         for (int i = 2; i < 11; i++)
1578         {
1579             rhoWriter.print("\nThis is line " + i + " in rho");
1580             rhoContent = rhoContent + "\nThis is line " + i + " in rho";
1581         }
1582         rhoWriter.close();
1583         backupTest.getWc().setItemWorkingCopyRevision("A/D/G/rho", 3);
1584         rhoContent = rhoContent + " Appended to line 10 of rho";
1585         backupTest.getWc().setItemContent("A/D/G/rho", rhoContent);
1586         backupTest.getWc().setItemTextStatus("A/D/G/rho", Status.Kind.modified);
1587 
1588         // update the backup working copy
1589         assertEquals("wrong revision number from update",
1590                      update(backupTest), 3);
1591 
1592         // check the status of the backup working copy
1593         backupTest.checkStatus();
1594     }
1595 
1596     /**
1597      * Test the basic SVNClient.update functionality with concurrent
1598      * changes in the repository and the working copy that generate
1599      * conflicts.
1600      * @throws Throwable
1601      */
testBasicConflict()1602     public void testBasicConflict() throws Throwable
1603     {
1604         // build the first working copy
1605         OneTest thisTest = new OneTest();
1606 
1607         // copy the first working copy to the backup working copy
1608         OneTest backupTest = thisTest.copy(".backup");
1609 
1610         // append a line to A/mu in the first working copy
1611         File mu = new File(thisTest.getWorkingCopy(), "A/mu");
1612         PrintWriter muWriter = new PrintWriter(new FileOutputStream(mu, true));
1613         String muContent = thisTest.getWc().getItemContent("A/mu");
1614         muWriter.print("\nOriginal appended text for mu");
1615         muContent = muContent + "\nOriginal appended text for mu";
1616         muWriter.close();
1617         thisTest.getWc().setItemWorkingCopyRevision("A/mu", 2);
1618         thisTest.getWc().setItemContent("A/mu", muContent);
1619         addExpectedCommitItem(thisTest.getWCPath(),
1620                               thisTest.getUrl().toString(), "A/mu", NodeKind.file,
1621                               CommitItemStateFlags.TextMods);
1622 
1623         // append a line to A/D/G/rho in the first working copy
1624         File rho = new File(thisTest.getWorkingCopy(), "A/D/G/rho");
1625         PrintWriter rhoWriter =
1626             new PrintWriter(new FileOutputStream(rho, true));
1627         String rhoContent = thisTest.getWc().getItemContent("A/D/G/rho");
1628         rhoWriter.print("\nOriginal appended text for rho");
1629         rhoContent = rhoContent + "\nOriginal appended text for rho";
1630         rhoWriter.close();
1631         thisTest.getWc().setItemWorkingCopyRevision("A/D/G/rho", 2);
1632         thisTest.getWc().setItemContent("A/D/G/rho", rhoContent);
1633         addExpectedCommitItem(thisTest.getWCPath(),
1634                               thisTest.getUrl().toString(), "A/D/G/rho", NodeKind.file,
1635                               CommitItemStateFlags.TextMods);
1636 
1637         // commit the changes in the first working copy
1638         checkCommitRevision(thisTest, "wrong revision number from commit", 2,
1639                             thisTest.getWCPathSet(), "log msg", Depth.infinity,
1640                             false, false, null, null);
1641 
1642         // test the status of the working copy after the commit
1643         thisTest.checkStatus();
1644 
1645         // append a different line to A/mu in the backup working copy
1646         mu = new File(backupTest.getWorkingCopy(), "A/mu");
1647         muWriter = new PrintWriter(new FileOutputStream(mu, true));
1648         muWriter.print("\nConflicting appended text for mu");
1649         muContent = "<<<<<<< .mine\nThis is the file 'mu'.\n"+
1650                     "Conflicting appended text for mu=======\n"+
1651                     "This is the file 'mu'.\n"+
1652                     "Original appended text for mu>>>>>>> .r2";
1653         muWriter.close();
1654         backupTest.getWc().setItemWorkingCopyRevision("A/mu", 2);
1655         backupTest.getWc().setItemContent("A/mu", muContent);
1656         backupTest.getWc().setItemTextStatus("A/mu", Status.Kind.conflicted);
1657         backupTest.getWc().addItem("A/mu.r1", "");
1658         backupTest.getWc().setItemNodeKind("A/mu.r1", NodeKind.unknown);
1659         backupTest.getWc().setItemTextStatus("A/mu.r1",
1660                                              Status.Kind.unversioned);
1661         backupTest.getWc().addItem("A/mu.r2", "");
1662         backupTest.getWc().setItemNodeKind("A/mu.r2", NodeKind.unknown);
1663         backupTest.getWc().setItemTextStatus("A/mu.r2",
1664                                              Status.Kind.unversioned);
1665         backupTest.getWc().addItem("A/mu.mine", "");
1666         backupTest.getWc().setItemNodeKind("A/mu.mine", NodeKind.unknown);
1667         backupTest.getWc().setItemTextStatus("A/mu.mine",
1668                                              Status.Kind.unversioned);
1669 
1670         // append a different line to A/D/G/rho in the backup working copy
1671         rho = new File(backupTest.getWorkingCopy(), "A/D/G/rho");
1672         rhoWriter = new PrintWriter(new FileOutputStream(rho, true));
1673         rhoWriter.print("\nConflicting appended text for rho");
1674         rhoContent = "<<<<<<< .mine\nThis is the file 'rho'.\n"+
1675                     "Conflicting appended text for rho=======\n"+
1676                     "his is the file 'rho'.\n"+
1677                     "Original appended text for rho>>>>>>> .r2";
1678         rhoWriter.close();
1679         backupTest.getWc().setItemWorkingCopyRevision("A/D/G/rho", 2);
1680         backupTest.getWc().setItemContent("A/D/G/rho", rhoContent);
1681         backupTest.getWc().setItemTextStatus("A/D/G/rho",
1682                                              Status.Kind.conflicted);
1683         backupTest.getWc().addItem("A/D/G/rho.r1", "");
1684         backupTest.getWc().setItemNodeKind("A/D/G/rho.r1", NodeKind.unknown);
1685         backupTest.getWc().setItemTextStatus("A/D/G/rho.r1",
1686                                              Status.Kind.unversioned);
1687         backupTest.getWc().addItem("A/D/G/rho.r2", "");
1688         backupTest.getWc().setItemNodeKind("A/D/G/rho.r2", NodeKind.unknown);
1689         backupTest.getWc().setItemTextStatus("A/D/G/rho.r2",
1690                                              Status.Kind.unversioned);
1691         backupTest.getWc().addItem("A/D/G/rho.mine", "");
1692         backupTest.getWc().setItemNodeKind("A/D/G/rho.mine", NodeKind.unknown);
1693         backupTest.getWc().setItemTextStatus("A/D/G/rho.mine",
1694                                              Status.Kind.unversioned);
1695 
1696         // update the backup working copy from the repository
1697         assertEquals("wrong revision number from update",
1698                      update(backupTest), 2);
1699 
1700         // check the status of the backup working copy
1701         backupTest.checkStatus();
1702 
1703         // flag A/mu as resolved
1704         client.resolve(backupTest.getWCPath()+"/A/mu", Depth.empty,
1705                 ConflictResult.Choice.chooseMerged);
1706         backupTest.getWc().setItemTextStatus("A/mu", Status.Kind.modified);
1707         backupTest.getWc().removeItem("A/mu.r1");
1708         backupTest.getWc().removeItem("A/mu.r2");
1709         backupTest.getWc().removeItem("A/mu.mine");
1710 
1711         // flag A/D/G/rho as resolved
1712         client.resolve(backupTest.getWCPath()+"/A/D/G/rho", Depth.empty,
1713                 ConflictResult.Choice.chooseMerged);
1714         backupTest.getWc().setItemTextStatus("A/D/G/rho",
1715                                              Status.Kind.modified);
1716         backupTest.getWc().removeItem("A/D/G/rho.r1");
1717         backupTest.getWc().removeItem("A/D/G/rho.r2");
1718         backupTest.getWc().removeItem("A/D/G/rho.mine");
1719 
1720         // check the status after the conflicts are flaged as resolved
1721         backupTest.checkStatus();
1722     }
1723 
1724     /**
1725      * Test the basic SVNClient.cleanup functionality.
1726      * Without a way to force a lock, this test just verifies
1727      * the method can be called successfully.
1728      * @throws Throwable
1729      */
testBasicCleanup()1730     public void testBasicCleanup() throws Throwable
1731     {
1732         // create a test working copy
1733         OneTest thisTest = new OneTest();
1734 
1735         // run cleanup
1736         client.cleanup(thisTest.getWCPath());
1737 
1738     }
1739 
1740     /**
1741      * Test the basic SVNClient.revert functionality.
1742      * @throws Throwable
1743      */
testBasicRevert()1744     public void testBasicRevert() throws Throwable
1745     {
1746         // create a test working copy
1747         OneTest thisTest = new OneTest();
1748 
1749         // modify A/B/E/beta
1750         File file = new File(thisTest.getWorkingCopy(), "A/B/E/beta");
1751         PrintWriter pw = new PrintWriter(new FileOutputStream(file, true));
1752         pw.print("Added some text to 'beta'.");
1753         pw.close();
1754         thisTest.getWc().setItemTextStatus("A/B/E/beta", Status.Kind.modified);
1755 
1756         // modify iota
1757         file = new File(thisTest.getWorkingCopy(), "iota");
1758         pw = new PrintWriter(new FileOutputStream(file, true));
1759         pw.print("Added some text to 'iota'.");
1760         pw.close();
1761         thisTest.getWc().setItemTextStatus("iota", Status.Kind.modified);
1762 
1763         // modify A/D/G/rho
1764         file = new File(thisTest.getWorkingCopy(), "A/D/G/rho");
1765         pw = new PrintWriter(new FileOutputStream(file, true));
1766         pw.print("Added some text to 'rho'.");
1767         pw.close();
1768         thisTest.getWc().setItemTextStatus("A/D/G/rho", Status.Kind.modified);
1769 
1770         // create new file A/D/H/zeta and add it to subversion
1771         file = new File(thisTest.getWorkingCopy(), "A/D/H/zeta");
1772         pw = new PrintWriter(new FileOutputStream(file, true));
1773         pw.print("Added some text to 'zeta'.");
1774         pw.close();
1775         thisTest.getWc().addItem("A/D/H/zeta", "Added some text to 'zeta'.");
1776         thisTest.getWc().setItemTextStatus("A/D/H/zeta", Status.Kind.added);
1777         client.add(file.getAbsolutePath(), Depth.empty, false, false, false);
1778 
1779         // test the status of the working copy
1780         thisTest.checkStatus();
1781 
1782         // revert the changes
1783         client.revert(thisTest.getWCPath()+"/A/B/E/beta", Depth.empty, null);
1784         thisTest.getWc().setItemTextStatus("A/B/E/beta", Status.Kind.normal);
1785         client.revert(thisTest.getWCPath()+"/iota", Depth.empty, null);
1786         thisTest.getWc().setItemTextStatus("iota", Status.Kind.normal);
1787         client.revert(thisTest.getWCPath()+"/A/D/G/rho", Depth.empty, null);
1788         thisTest.getWc().setItemTextStatus("A/D/G/rho", Status.Kind.normal);
1789         client.revert(thisTest.getWCPath()+"/A/D/H/zeta", Depth.empty, null);
1790         thisTest.getWc().setItemTextStatus("A/D/H/zeta",
1791                 Status.Kind.unversioned);
1792         thisTest.getWc().setItemNodeKind("A/D/H/zeta", NodeKind.unknown);
1793 
1794         // test the status of the working copy
1795         thisTest.checkStatus();
1796 
1797         // delete A/B/E/beta and revert the change
1798         file = new File(thisTest.getWorkingCopy(), "A/B/E/beta");
1799         file.delete();
1800         client.revert(file.getAbsolutePath(), Depth.empty, null);
1801 
1802         // resurected file should not be readonly
1803         assertTrue("reverted file is not readonly",
1804                 file.canWrite()&& file.canRead());
1805 
1806         // test the status of the working copy
1807         thisTest.checkStatus();
1808 
1809         // create & add the directory X
1810         client.mkdir(thisTest.getWCPathSet("/X"), false, null, null, null);
1811         thisTest.getWc().addItem("X", null);
1812         thisTest.getWc().setItemTextStatus("X", Status.Kind.added);
1813 
1814         // test the status of the working copy
1815         thisTest.checkStatus();
1816 
1817         // remove & revert X
1818         removeDirOrFile(new File(thisTest.getWorkingCopy(), "X"));
1819         client.revert(thisTest.getWCPath()+"/X", Depth.empty, null);
1820         thisTest.getWc().removeItem("X");
1821 
1822         // test the status of the working copy
1823         thisTest.checkStatus();
1824 
1825         // delete the directory A/B/E
1826         client.remove(thisTest.getWCPathSet("/A/B/E"), true,
1827                       false, null, null, null);
1828         removeDirOrFile(new File(thisTest.getWorkingCopy(), "A/B/E"));
1829         thisTest.getWc().setItemTextStatus("A/B/E", Status.Kind.deleted);
1830         thisTest.getWc().setItemTextStatus("A/B/E/alpha", Status.Kind.deleted);
1831         thisTest.getWc().setItemTextStatus("A/B/E/beta", Status.Kind.deleted);
1832 
1833         // test the status of the working copy
1834         thisTest.checkStatus();
1835 
1836         // revert A/B/E -> this will resurect it
1837         client.revert(thisTest.getWCPath()+"/A/B/E", Depth.infinity, null);
1838         thisTest.getWc().setItemTextStatus("A/B/E", Status.Kind.normal);
1839         thisTest.getWc().setItemTextStatus("A/B/E/alpha", Status.Kind.normal);
1840         thisTest.getWc().setItemTextStatus("A/B/E/beta", Status.Kind.normal);
1841 
1842         // test the status of the working copy
1843         thisTest.checkStatus();
1844     }
1845 
1846     /**
1847      * Test the basic SVNClient.switch functionality.
1848      * @throws Throwable
1849      */
testBasicSwitch()1850     public void testBasicSwitch() throws Throwable
1851     {
1852         // create the test working copy
1853         OneTest thisTest = new OneTest();
1854 
1855         // switch iota to A/D/gamma
1856         String iotaPath = thisTest.getWCPath() + "/iota";
1857         String gammaUrl = thisTest.getUrl() + "/A/D/gamma";
1858         thisTest.getWc().setItemContent("iota",
1859                 greekWC.getItemContent("A/D/gamma"));
1860         thisTest.getWc().setItemIsSwitched("iota", true);
1861         client.doSwitch(iotaPath, gammaUrl, null, Revision.HEAD, Depth.unknown,
1862                         false, false, false, true);
1863 
1864         // check the status of the working copy
1865         thisTest.checkStatus();
1866 
1867         // switch A/D/H to /A/D/G
1868         String adhPath = thisTest.getWCPath() + "/A/D/H";
1869         String adgURL = thisTest.getUrl() + "/A/D/G";
1870         thisTest.getWc().setItemIsSwitched("A/D/H",true);
1871         thisTest.getWc().removeItem("A/D/H/chi");
1872         thisTest.getWc().removeItem("A/D/H/omega");
1873         thisTest.getWc().removeItem("A/D/H/psi");
1874         thisTest.getWc().addItem("A/D/H/pi",
1875                 thisTest.getWc().getItemContent("A/D/G/pi"));
1876         thisTest.getWc().addItem("A/D/H/rho",
1877                 thisTest.getWc().getItemContent("A/D/G/rho"));
1878         thisTest.getWc().addItem("A/D/H/tau",
1879                 thisTest.getWc().getItemContent("A/D/G/tau"));
1880         client.doSwitch(adhPath, adgURL, null, Revision.HEAD, Depth.files,
1881                         false, false, false, true);
1882 
1883         // check the status of the working copy
1884         thisTest.checkStatus();
1885     }
1886 
1887     /**
1888      * Test the basic SVNClient.remove functionality.
1889      * @throws Throwable
1890      */
testBasicDelete()1891     public void testBasicDelete() throws Throwable
1892     {
1893         // create the test working copy
1894         OneTest thisTest = new OneTest();
1895 
1896         // modify A/D/H/chi
1897         File file = new File(thisTest.getWorkingCopy(), "A/D/H/chi");
1898         Set<String> pathSet = new HashSet<String>();
1899         PrintWriter pw = new PrintWriter(new FileOutputStream(file, true));
1900         pw.print("added to chi");
1901         pw.close();
1902         thisTest.getWc().setItemTextStatus("A/D/H/chi", Status.Kind.modified);
1903 
1904         // set a property on A/D/G/rho file
1905         pathSet.clear();
1906         pathSet.add(thisTest.getWCPath()+"/A/D/G/rho");
1907         client.propertySetLocal(pathSet, "abc", (new String("def")).getBytes(),
1908                                 Depth.infinity, null, false);
1909         thisTest.getWc().setItemPropStatus("A/D/G/rho", Status.Kind.modified);
1910 
1911         // set a property on A/B/F directory
1912         setprop(thisTest.getWCPath()+"/A/B/F", "abc", "def");
1913         thisTest.getWc().setItemPropStatus("A/B/F", Status.Kind.modified);
1914 
1915         // create a unversioned A/C/sigma file
1916         file = new File(thisTest.getWCPath(),"A/C/sigma");
1917         pw = new PrintWriter(new FileOutputStream(file));
1918         pw.print("unversioned sigma");
1919         pw.close();
1920         thisTest.getWc().addItem("A/C/sigma", "unversioned sigma");
1921         thisTest.getWc().setItemTextStatus("A/C/sigma", Status.Kind.unversioned);
1922         thisTest.getWc().setItemNodeKind("A/C/sigma", NodeKind.unknown);
1923 
1924         // create unversioned directory A/C/Q
1925         file = new File(thisTest.getWCPath(), "A/C/Q");
1926         file.mkdir();
1927         thisTest.getWc().addItem("A/C/Q", null);
1928         thisTest.getWc().setItemNodeKind("A/C/Q", NodeKind.unknown);
1929         thisTest.getWc().setItemTextStatus("A/C/Q", Status.Kind.unversioned);
1930 
1931         // create & add the directory A/B/X
1932         file = new File(thisTest.getWCPath(), "A/B/X");
1933         pathSet.clear();
1934         pathSet.add(file.getAbsolutePath());
1935         client.mkdir(pathSet, false, null, null, null);
1936         thisTest.getWc().addItem("A/B/X", null);
1937         thisTest.getWc().setItemTextStatus("A/B/X", Status.Kind.added);
1938 
1939         // create & add the file A/B/X/xi
1940         file = new File(file, "xi");
1941         pw = new PrintWriter(new FileOutputStream(file));
1942         pw.print("added xi");
1943         pw.close();
1944         client.add(file.getAbsolutePath(), Depth.empty, false, false, false);
1945         thisTest.getWc().addItem("A/B/X/xi", "added xi");
1946         thisTest.getWc().setItemTextStatus("A/B/X/xi", Status.Kind.added);
1947 
1948         // create & add the directory A/B/Y
1949         file = new File(thisTest.getWCPath(), "A/B/Y");
1950         pathSet.clear();
1951         pathSet.add(file.getAbsolutePath());
1952         client.mkdir(pathSet, false, null, null, null);
1953         thisTest.getWc().addItem("A/B/Y", null);
1954         thisTest.getWc().setItemTextStatus("A/B/Y", Status.Kind.added);
1955 
1956         // test the status of the working copy
1957         thisTest.checkStatus();
1958 
1959         // the following removes should all fail without force
1960 
1961         try
1962         {
1963             // remove of A/D/H/chi without force should fail, because it is
1964             // modified
1965             client.remove(thisTest.getWCPathSet("/A/D/H/chi"),
1966                     false, false, null, null, null);
1967             fail("missing exception");
1968         }
1969         catch(ClientException expected)
1970         {
1971         }
1972 
1973         try
1974         {
1975             // remove of A/D/H without force should fail, because A/D/H/chi is
1976             // modified
1977             client.remove(thisTest.getWCPathSet("/A/D/H"),
1978                     false, false, null, null, null);
1979             fail("missing exception");
1980         }
1981         catch(ClientException expected)
1982         {
1983         }
1984 
1985         try
1986         {
1987             // remove of A/D/G/rho without force should fail, because it has
1988             // a new property
1989             client.remove(thisTest.getWCPathSet("/A/D/G/rho"),
1990                     false, false, null, null, null);
1991             fail("missing exception");
1992         }
1993         catch(ClientException expected)
1994         {
1995         }
1996 
1997         try
1998         {
1999             // remove of A/D/G without force should fail, because A/D/G/rho has
2000             // a new property
2001             client.remove(thisTest.getWCPathSet("/A/D/G"),
2002                     false, false, null, null, null);
2003             fail("missing exception");
2004         }
2005         catch(ClientException expected)
2006         {
2007         }
2008 
2009         try
2010         {
2011             // remove of A/B/F without force should fail, because it has
2012             // a new property
2013             client.remove(thisTest.getWCPathSet("/A/B/F"),
2014                     false, false, null, null, null);
2015             fail("missing exception");
2016         }
2017         catch(ClientException expected)
2018         {
2019         }
2020 
2021         try
2022         {
2023             // remove of A/B without force should fail, because A/B/F has
2024             // a new property
2025             client.remove(thisTest.getWCPathSet("/A/B"),
2026                     false, false, null, null, null);
2027             fail("missing exception");
2028         }
2029         catch(ClientException expected)
2030         {
2031         }
2032 
2033         try
2034         {
2035             // remove of A/C/sigma without force should fail, because it is
2036             // unversioned
2037             client.remove(thisTest.getWCPathSet("/A/C/sigma"),
2038                           false, false, null, null, null);
2039             fail("missing exception");
2040         }
2041         catch(ClientException expected)
2042         {
2043         }
2044 
2045         try
2046         {
2047             // remove of A/C without force should fail, because A/C/sigma is
2048             // unversioned
2049             client.remove(thisTest.getWCPathSet("/A/C"),
2050                           false, false, null, null, null);
2051             fail("missing exception");
2052         }
2053         catch(ClientException expected)
2054         {
2055         }
2056 
2057         try
2058         {
2059             // remove of A/B/X without force should fail, because it is new
2060             client.remove(thisTest.getWCPathSet("/A/B/X"),
2061                           false, false, null, null, null);
2062             fail("missing exception");
2063         }
2064         catch(ClientException expected)
2065         {
2066         }
2067 
2068         // check the status of the working copy
2069         thisTest.checkStatus();
2070 
2071         // the following removes should all work
2072         client.remove(thisTest.getWCPathSet("/A/B/E"),
2073                       false, false, null, null, null);
2074         thisTest.getWc().setItemTextStatus("A/B/E",Status.Kind.deleted);
2075         thisTest.getWc().setItemTextStatus("A/B/E/alpha",Status.Kind.deleted);
2076         thisTest.getWc().setItemTextStatus("A/B/E/beta",Status.Kind.deleted);
2077         client.remove(thisTest.getWCPathSet("/A/D/H"), true,
2078                       false, null, null, null);
2079         thisTest.getWc().setItemTextStatus("A/D/H",Status.Kind.deleted);
2080         thisTest.getWc().setItemTextStatus("A/D/H/chi",Status.Kind.deleted);
2081         thisTest.getWc().setItemTextStatus("A/D/H/omega",Status.Kind.deleted);
2082         thisTest.getWc().setItemTextStatus("A/D/H/psi",Status.Kind.deleted);
2083         client.remove(thisTest.getWCPathSet("/A/D/G"), true,
2084                       false, null, null, null);
2085         thisTest.getWc().setItemTextStatus("A/D/G",Status.Kind.deleted);
2086         thisTest.getWc().setItemTextStatus("A/D/G/rho",Status.Kind.deleted);
2087         thisTest.getWc().setItemPropStatus("A/D/G/rho", Status.Kind.none);
2088         thisTest.getWc().setItemTextStatus("A/D/G/pi",Status.Kind.deleted);
2089         thisTest.getWc().setItemTextStatus("A/D/G/tau",Status.Kind.deleted);
2090         client.remove(thisTest.getWCPathSet("/A/B/F"), true,
2091                       false, null, null, null);
2092         thisTest.getWc().setItemTextStatus("A/B/F",Status.Kind.deleted);
2093         thisTest.getWc().setItemPropStatus("A/B/F", Status.Kind.none);
2094         client.remove(thisTest.getWCPathSet("/A/C"), true,
2095                       false, null, null, null);
2096         thisTest.getWc().setItemTextStatus("A/C",Status.Kind.deleted);
2097         client.remove(thisTest.getWCPathSet("/A/B/X"), true,
2098                       false, null, null, null);
2099         file = new File(thisTest.getWorkingCopy(), "iota");
2100         file.delete();
2101         pathSet.clear();
2102         pathSet.add(file.getAbsolutePath());
2103         client.remove(pathSet, true, false, null, null, null);
2104         thisTest.getWc().setItemTextStatus("iota",Status.Kind.deleted);
2105         file = new File(thisTest.getWorkingCopy(), "A/D/gamma");
2106         file.delete();
2107         pathSet.clear();
2108         pathSet.add(file.getAbsolutePath());
2109         client.remove(pathSet, false, false, null, null, null);
2110         thisTest.getWc().setItemTextStatus("A/D/gamma",Status.Kind.deleted);
2111         pathSet.clear();
2112         pathSet.add(file.getAbsolutePath());
2113         client.remove(pathSet, true, false, null, null, null);
2114         client.remove(thisTest.getWCPathSet("/A/B/E"),
2115                       false, false, null, null, null);
2116         thisTest.getWc().removeItem("A/B/X");
2117         thisTest.getWc().removeItem("A/B/X/xi");
2118         thisTest.getWc().removeItem("A/C/sigma");
2119         thisTest.getWc().removeItem("A/C/Q");
2120         thisTest.checkStatus();
2121         client.remove(thisTest.getWCPathSet("/A/D"), true,
2122                       false, null, null, null);
2123         thisTest.getWc().setItemTextStatus("A/D", Status.Kind.deleted);
2124         thisTest.getWc().removeItem("A/D/Y");
2125 
2126         // check the status of the working copy
2127         thisTest.checkStatus();
2128 
2129         // confirm that the file are really deleted
2130         assertFalse("failed to remove text modified file",
2131                 new File(thisTest.getWorkingCopy(), "A/D/G/rho").exists());
2132         assertFalse("failed to remove prop modified file",
2133                 new File(thisTest.getWorkingCopy(), "A/D/H/chi").exists());
2134         assertFalse("failed to remove unversioned file",
2135                 new File(thisTest.getWorkingCopy(), "A/C/sigma").exists());
2136         assertFalse("failed to remove unmodified file",
2137                 new File(thisTest.getWorkingCopy(), "A/B/E/alpha").exists());
2138         file = new File(thisTest.getWorkingCopy(),"A/B/F");
2139         assertFalse("failed to remove versioned dir", file.exists());
2140         assertFalse("failed to remove unversioned dir",
2141                 new File(thisTest.getWorkingCopy(), "A/C/Q").exists());
2142         assertFalse("failed to remove added dir",
2143                 new File(thisTest.getWorkingCopy(), "A/B/X").exists());
2144 
2145         // delete unversioned file foo
2146         file = new File(thisTest.getWCPath(),"foo");
2147         pw = new PrintWriter(new FileOutputStream(file));
2148         pw.print("unversioned foo");
2149         pw.close();
2150         pathSet.clear();
2151         pathSet.add(file.getAbsolutePath());
2152         client.remove(pathSet, true, false, null, null, null);
2153         assertFalse("failed to remove unversioned file foo", file.exists());
2154 
2155         try
2156         {
2157             // delete non-existent file foo
2158             Set<String> paths = new HashSet<String>(1);
2159             paths.add(file.getAbsolutePath());
2160             client.remove(paths, true, false, null, null, null);
2161             fail("missing exception");
2162         }
2163         catch(ClientException expected)
2164         {
2165         }
2166 
2167         // delete file iota in the repository
2168         addExpectedCommitItem(null, thisTest.getUrl().toString(), "iota",
2169                              NodeKind.none, CommitItemStateFlags.Delete);
2170         client.remove(thisTest.getUrlSet("/iota"), false, false, null,
2171                       new ConstMsg("delete iota URL"), null);
2172     }
2173 
testBasicCheckoutDeleted()2174     public void testBasicCheckoutDeleted() throws Throwable
2175     {
2176         // create working copy
2177         OneTest thisTest = new OneTest();
2178 
2179         // delete A/D and its content
2180         client.remove(thisTest.getWCPathSet("/A/D"), true,
2181                       false, null, null, null);
2182         thisTest.getWc().setItemTextStatus("A/D", Status.Kind.deleted);
2183         thisTest.getWc().setItemTextStatus("A/D/G", Status.Kind.deleted);
2184         thisTest.getWc().setItemTextStatus("A/D/G/rho", Status.Kind.deleted);
2185         thisTest.getWc().setItemTextStatus("A/D/G/pi", Status.Kind.deleted);
2186         thisTest.getWc().setItemTextStatus("A/D/G/tau", Status.Kind.deleted);
2187         thisTest.getWc().setItemTextStatus("A/D/H", Status.Kind.deleted);
2188         thisTest.getWc().setItemTextStatus("A/D/H/chi", Status.Kind.deleted);
2189         thisTest.getWc().setItemTextStatus("A/D/H/psi", Status.Kind.deleted);
2190         thisTest.getWc().setItemTextStatus("A/D/H/omega", Status.Kind.deleted);
2191         thisTest.getWc().setItemTextStatus("A/D/gamma", Status.Kind.deleted);
2192 
2193         // check the working copy status
2194         thisTest.checkStatus();
2195 
2196         // commit the change
2197         addExpectedCommitItem(thisTest.getWCPath(),
2198                 thisTest.getUrl().toString(), "A/D", NodeKind.dir,
2199                 CommitItemStateFlags.Delete);
2200         checkCommitRevision(thisTest, "wrong revision from commit", 2,
2201                             thisTest.getWCPathSet(), "log message",
2202                             Depth.infinity, false, false, null, null);
2203         thisTest.getWc().removeItem("A/D");
2204         thisTest.getWc().removeItem("A/D/G");
2205         thisTest.getWc().removeItem("A/D/G/rho");
2206         thisTest.getWc().removeItem("A/D/G/pi");
2207         thisTest.getWc().removeItem("A/D/G/tau");
2208         thisTest.getWc().removeItem("A/D/H");
2209         thisTest.getWc().removeItem("A/D/H/chi");
2210         thisTest.getWc().removeItem("A/D/H/psi");
2211         thisTest.getWc().removeItem("A/D/H/omega");
2212         thisTest.getWc().removeItem("A/D/gamma");
2213 
2214         // check the working copy status
2215         thisTest.checkStatus();
2216 
2217         // check out the previous revision
2218         client.checkout(thisTest.getUrl()+"/A/D",
2219                 thisTest.getWCPath()+"/new_D", new Revision.Number(1),
2220                 new Revision.Number(1), Depth.infinity, false, false);
2221     }
2222 
2223     /**
2224      * Test the basic SVNClient.import functionality.
2225      * @throws Throwable
2226      */
testBasicImport()2227     public void testBasicImport() throws Throwable
2228     {
2229         // create the working copy
2230         OneTest thisTest = new OneTest();
2231 
2232         // create new_file
2233         File file = new File(thisTest.getWCPath(),"new_file");
2234         PrintWriter pw = new PrintWriter(new FileOutputStream(file));
2235         pw.print("some text");
2236         pw.close();
2237 
2238         // import new_file info dirA/dirB/newFile
2239         addExpectedCommitItem(thisTest.getWCPath(),
2240                 null, "new_file", NodeKind.none, CommitItemStateFlags.Add);
2241         client.doImport(file.getAbsolutePath(),
2242                 thisTest.getUrl()+"/dirA/dirB/new_file", Depth.infinity,
2243                 false, false, null,
2244                 new ConstMsg("log message for new import"), null);
2245 
2246         // delete new_file
2247         file.delete();
2248 
2249         // update the working
2250         assertEquals("wrong revision from update",
2251                      update(thisTest), 2);
2252         thisTest.getWc().addItem("dirA", null);
2253         thisTest.getWc().setItemWorkingCopyRevision("dirA",2);
2254         thisTest.getWc().addItem("dirA/dirB", null);
2255         thisTest.getWc().setItemWorkingCopyRevision("dirA/dirB",2);
2256         thisTest.getWc().addItem("dirA/dirB/new_file", "some text");
2257         thisTest.getWc().setItemWorkingCopyRevision("dirA/dirB/new_file",2);
2258 
2259         // test the working copy status
2260         thisTest.checkStatus();
2261     }
2262 
2263     /**
2264      * Test the basic SVNClient.fileContent functionality.
2265      * @throws Throwable
2266      */
testBasicCat()2267     public void testBasicCat() throws Throwable
2268     {
2269         // create the working copy
2270         OneTest thisTest = new OneTest();
2271 
2272         // modify A/mu
2273         File mu = new File(thisTest.getWorkingCopy(), "A/mu");
2274         PrintWriter pw = new PrintWriter(new FileOutputStream(mu, true));
2275         pw.print("some text");
2276         pw.close();
2277         // get the content from the repository
2278         byte[] content = client.fileContent(thisTest.getWCPath()+"/A/mu", null,
2279                                     null);
2280         byte[] testContent = thisTest.getWc().getItemContent("A/mu").getBytes();
2281 
2282         // the content should be the same
2283         assertTrue("content changed", Arrays.equals(content, testContent));
2284     }
2285 
2286     /**
2287      * Test the basic SVNClient.fileContent functionality.
2288      * @throws Throwable
2289      */
testBasicCatStream()2290     public void testBasicCatStream() throws Throwable
2291     {
2292         // create the working copy
2293         OneTest thisTest = new OneTest();
2294 
2295         // modify A/mu
2296         File mu = new File(thisTest.getWorkingCopy(), "A/mu");
2297         PrintWriter pw = new PrintWriter(new FileOutputStream(mu, true));
2298         pw.print("some text");
2299         pw.close();
2300         // get the content from the repository
2301         ByteArrayOutputStream baos = new ByteArrayOutputStream();
2302         client.streamFileContent(thisTest.getWCPath() + "/A/mu", null, null,
2303                                  baos);
2304 
2305         byte[] content = baos.toByteArray();
2306         byte[] testContent = thisTest.getWc().getItemContent("A/mu").getBytes();
2307 
2308         // the content should be the same
2309         assertTrue("content changed", Arrays.equals(content, testContent));
2310     }
2311 
2312     /**
2313      * Test the basic SVNClient.list functionality.
2314      * @throws Throwable
2315      */
testBasicLs()2316     public void testBasicLs() throws Throwable
2317     {
2318         // create the working copy
2319         OneTest thisTest = new OneTest();
2320 
2321         // list the repository root dir
2322         DirEntry[] entries = collectDirEntries(thisTest.getWCPath(), null,
2323                                                null, Depth.immediates,
2324                                                DirEntry.Fields.all, false);
2325         thisTest.getWc().check(entries, "", false);
2326 
2327         // list directory A
2328         entries = collectDirEntries(thisTest.getWCPath() + "/A", null, null,
2329                                     Depth.immediates, DirEntry.Fields.all,
2330                                     false);
2331         thisTest.getWc().check(entries, "A", false);
2332 
2333         // list directory A in BASE revision
2334         entries = collectDirEntries(thisTest.getWCPath() + "/A", Revision.BASE,
2335                                     Revision.BASE, Depth.immediates,
2336                                     DirEntry.Fields.all, false);
2337         thisTest.getWc().check(entries, "A", false);
2338 
2339         // list file A/mu
2340         entries = collectDirEntries(thisTest.getWCPath() + "/A/mu", null, null,
2341                                     Depth.immediates, DirEntry.Fields.all,
2342                                     false);
2343         thisTest.getWc().check(entries, "A/mu");
2344     }
2345 
2346     /**
2347      * Test the basis SVNClient.add functionality with files that
2348      * should be ignored.
2349      * @throws Throwable
2350      */
testBasicAddIgnores()2351     public void testBasicAddIgnores() throws Throwable
2352     {
2353         // create working copy
2354         OneTest thisTest = new OneTest();
2355 
2356         // create dir
2357         File dir = new File(thisTest.getWorkingCopy(), "dir");
2358         dir.mkdir();
2359 
2360         // create dir/foo.c
2361         File fileC = new File(dir, "foo.c");
2362         new FileOutputStream(fileC).close();
2363 
2364         // create dir/foo.o (should be ignored)
2365         File fileO = new File(dir, "foo.o");
2366         new FileOutputStream(fileO).close();
2367 
2368         // add dir
2369         client.add(dir.getAbsolutePath(), Depth.infinity, false, false, false);
2370         thisTest.getWc().addItem("dir", null);
2371         thisTest.getWc().setItemTextStatus("dir",Status.Kind.added);
2372         thisTest.getWc().addItem("dir/foo.c", "");
2373         thisTest.getWc().setItemTextStatus("dir/foo.c",Status.Kind.added);
2374         thisTest.getWc().addItem("dir/foo.o", "");
2375         thisTest.getWc().setItemTextStatus("dir/foo.o",Status.Kind.ignored);
2376         thisTest.getWc().setItemNodeKind("dir/foo.o", NodeKind.unknown);
2377 
2378         // test the working copy status
2379         thisTest.checkStatus();
2380     }
2381 
2382     /**
2383      * Test the basis SVNClient.import functionality with files that
2384      * should be ignored.
2385      * @throws Throwable
2386      */
testBasicImportIgnores()2387     public void testBasicImportIgnores() throws Throwable
2388     {
2389         // create working copy
2390         OneTest thisTest = new OneTest();
2391 
2392         // create dir
2393         File dir = new File(thisTest.getWorkingCopy(), "dir");
2394         dir.mkdir();
2395 
2396         // create dir/foo.c
2397         File fileC = new File(dir, "foo.c");
2398         new FileOutputStream(fileC).close();
2399 
2400         // create dir/foo.o (should be ignored)
2401         File fileO = new File(dir, "foo.o");
2402         new FileOutputStream(fileO).close();
2403 
2404         // import dir
2405         addExpectedCommitItem(thisTest.getWCPath(),
2406                 null, "dir", NodeKind.none, CommitItemStateFlags.Add);
2407         client.doImport(dir.getAbsolutePath(), thisTest.getUrl()+"/dir",
2408                 Depth.infinity, false, false, null,
2409                 new ConstMsg("log message for import"), null);
2410 
2411         // remove dir
2412         removeDirOrFile(dir);
2413 
2414         // udpate the working copy
2415         assertEquals("wrong revision from update",
2416                      update(thisTest), 2);
2417         thisTest.getWc().addItem("dir", null);
2418         thisTest.getWc().addItem("dir/foo.c", "");
2419 
2420         // test the working copy status
2421         thisTest.checkStatus();
2422     }
2423 
2424     /**
2425      * Test the basic SVNClient.info functionality.
2426      * @throws Throwable
2427      */
testBasicInfo()2428     public void testBasicInfo() throws Throwable
2429     {
2430         // create the working copy
2431         OneTest thisTest = new OneTest();
2432 
2433         // get the item information and test it
2434         Info info = collectInfos(thisTest.getWCPath()+"/A/mu", null, null,
2435                                   Depth.empty, null)[0];
2436         assertEquals("wrong revision from info", 1,
2437                      info.getLastChangedRev());
2438         assertEquals("wrong schedule kind from info",
2439                      Info.ScheduleKind.normal, info.getSchedule());
2440         assertEquals("wrong node kind from info", NodeKind.file,
2441                      info.getKind());
2442     }
2443 
2444     /**
2445      * Test the basic SVNClient.logMessages functionality.
2446      * @throws Throwable
2447      */
testBasicLogMessage()2448     public void testBasicLogMessage() throws Throwable
2449     {
2450         // create the working copy
2451         OneTest thisTest = new OneTest();
2452 
2453         // get the commit message of the initial import and test it
2454         List<RevisionRange> ranges = new ArrayList<RevisionRange>(1);
2455         ranges.add(new RevisionRange(null, null));
2456         LogMessage lm[] = collectLogMessages(thisTest.getWCPath(), null,
2457                                              ranges, false, true, false, 0);
2458         assertEquals("wrong number of objects", 1, lm.length);
2459         assertEquals("wrong message", "Log Message", lm[0].getMessage());
2460         assertEquals("wrong revision", 1, lm[0].getRevisionNumber());
2461         assertEquals("wrong user", "jrandom", lm[0].getAuthor());
2462         assertNotNull("changed paths set", lm[0].getChangedPaths());
2463         Set<ChangePath> cp = lm[0].getChangedPaths();
2464         assertEquals("wrong number of chang pathes", 20, cp.size());
2465         ChangePath changedApath = cp.toArray(new ChangePath[1])[0];
2466         assertNotNull("wrong path", changedApath);
2467         assertEquals("wrong copy source rev", -1,
2468                       changedApath.getCopySrcRevision());
2469         assertNull("wrong copy source path", changedApath.getCopySrcPath());
2470         assertEquals("wrong action", ChangePath.Action.add,
2471                      changedApath.getAction());
2472         assertEquals("wrong time with getTimeMicros()",
2473                      lm[0].getTimeMicros()/1000,
2474                      lm[0].getDate().getTime());
2475         assertEquals("wrong time with getTimeMillis()",
2476                      lm[0].getTimeMillis(),
2477                      lm[0].getDate().getTime());
2478         assertEquals("wrong date with getTimeMicros()",
2479                      lm[0].getDate(),
2480                      new java.util.Date(lm[0].getTimeMicros()/1000));
2481         assertEquals("wrong date with getTimeMillis()",
2482                      lm[0].getDate(),
2483                      new java.util.Date(lm[0].getTimeMillis()));
2484 
2485         // Ensure that targets get canonicalized
2486         String non_canonical = thisTest.getUrl().toString() + "/";
2487         LogMessage lm2[] = collectLogMessages(non_canonical, null,
2488                                               ranges, false, true, false, 0);
2489     }
2490 
2491     /**
2492      * Test the basic SVNClient.getVersionInfo functionality.
2493      * @throws Throwable
2494      * @since 1.2
2495      */
testBasicVersionInfo()2496     public void testBasicVersionInfo() throws Throwable
2497     {
2498         // create the working copy
2499         OneTest thisTest = new OneTest();
2500         assertEquals("wrong version info",
2501                      "1",
2502                      client.getVersionInfo(thisTest.getWCPath(), null, false));
2503     }
2504 
2505     /**
2506      * Test the basic SVNClient locking functionality.
2507      * @throws Throwable
2508      * @since 1.2
2509      */
testBasicLocking()2510     public void testBasicLocking() throws Throwable
2511     {
2512         // build the first working copy
2513         OneTest thisTest = new OneTest();
2514         Set<String> muPathSet = new HashSet<String>(1);
2515         muPathSet.add(thisTest.getWCPath()+"/A/mu");
2516 
2517         setprop(thisTest.getWCPath()+"/A/mu", Property.NEEDS_LOCK, "*");
2518 
2519         addExpectedCommitItem(thisTest.getWCPath(),
2520                               thisTest.getUrl().toString(), "A/mu",NodeKind.file,
2521                               CommitItemStateFlags.PropMods);
2522         checkCommitRevision(thisTest, "bad revision number on commit", 2,
2523                             thisTest.getWCPathSet(), "message", Depth.infinity,
2524                             false, false, null, null);
2525         File f = new File(thisTest.getWCPath()+"/A/mu");
2526         assertEquals("file should be read only now", false, f.canWrite());
2527         client.lock(muPathSet, "comment", false);
2528         assertEquals("file should be read write now", true, f.canWrite());
2529         client.unlock(muPathSet, false);
2530         assertEquals("file should be read only now", false, f.canWrite());
2531         client.lock(muPathSet, "comment", false);
2532         assertEquals("file should be read write now", true, f.canWrite());
2533         addExpectedCommitItem(thisTest.getWCPath(),
2534                               thisTest.getUrl().toString(), "A/mu",
2535                               NodeKind.file, 0);
2536         checkCommitRevision(thisTest, "rev number from commit", -1,
2537                             thisTest.getWCPathSet(), "message", Depth.infinity,
2538                             false, false, null, null);
2539         assertEquals("file should be read write now", true, f.canWrite());
2540 
2541         try
2542         {
2543             // Attempt to lock an invalid path
2544             client.lock(thisTest.getWCPathSet("/A/mu2"), "comment", false);
2545             fail("missing exception");
2546         }
2547         catch (ClientException expected)
2548         {
2549         }
2550     }
2551 
2552     /**
2553      * Test the basic SVNClient.info functionality.
2554      * @throws Throwable
2555      * @since 1.2
2556      */
testBasicInfo2()2557     public void testBasicInfo2() throws Throwable
2558     {
2559         // build the first working copy
2560         OneTest thisTest = new OneTest();
2561 
2562         final String failureMsg = "Incorrect number of info objects";
2563         Info[] infos = collectInfos(thisTest.getWCPath(), null, null,
2564                                      Depth.empty, null);
2565         assertEquals(failureMsg, 1, infos.length);
2566         infos = collectInfos(thisTest.getWCPath(), null, null, Depth.infinity,
2567                              null);
2568         assertEquals(failureMsg, 21, infos.length);
2569         for (Info info : infos)
2570         {
2571             assertNull("Unexpected changelist present",
2572                        info.getChangelistName());
2573 
2574             boolean isFile = info.getKind() == NodeKind.file;
2575             assertTrue("Unexpected working file size " + info.getWorkingSize()
2576                        + " for '" + info + '\'',
2577                        (isFile ? info.getWorkingSize() > -1 :
2578                         info.getWorkingSize() == -1));
2579             // We shouldn't know the repository file size when only
2580             // examining the WC.
2581             assertEquals("Unexpected repos file size for '" + info + '\'',
2582                          -1, info.getReposSize());
2583 
2584            // Examine depth
2585            assertEquals("Unexpected depth for '" + info + "'",
2586                         (isFile ? Depth.unknown : Depth.infinity),
2587                         info.getDepth());
2588         }
2589 
2590         // Create wc with a depth of Depth.empty
2591         String secondWC = thisTest.getWCPath() + ".empty";
2592         removeDirOrFile(new File(secondWC));
2593 
2594         client.checkout(thisTest.getUrl().toString(), secondWC, null, null,
2595                        Depth.empty, false, true);
2596 
2597         infos = collectInfos(secondWC, null, null, Depth.empty, null);
2598 
2599         // Examine that depth is Depth.empty
2600         assertEquals(Depth.empty, infos[0].getDepth());
2601     }
2602 
2603     /**
2604      * Test basic changelist functionality.
2605      * @throws Throwable
2606      * @since 1.5
2607      */
testBasicChangelist()2608     public void testBasicChangelist() throws Throwable
2609     {
2610         // build the working copy
2611         OneTest thisTest = new OneTest();
2612         String changelistName = "changelist1";
2613         Collection<String> changelists = new ArrayList<String>();
2614         changelists.add(changelistName);
2615         MyChangelistCallback clCallback = new MyChangelistCallback();
2616 
2617         String path = fileToSVNPath(new File(thisTest.getWCPath(), "iota"),
2618                                     true);
2619         Set<String> paths = new HashSet<String>(1);
2620         paths.add(path);
2621         // Add a path to a changelist, and check to see if it got added
2622         client.addToChangelist(paths, changelistName, Depth.infinity, null);
2623         client.getChangelists(thisTest.getWCPath(), changelists,
2624                               Depth.infinity, clCallback);
2625         Collection<String> cl = clCallback.get(path);
2626         assertTrue(changelists.equals(cl));
2627         // Does status report this changelist?
2628         MyStatusCallback statusCallback = new MyStatusCallback();
2629         client.status(path, Depth.immediates,
2630                       false, true, false, false, false, false,
2631                       null, statusCallback);
2632         Status[] status = statusCallback.getStatusArray();
2633         assertEquals(status[0].getChangelist(), changelistName);
2634 
2635         // Remove the path from the changelist, and check to see if the path is
2636         // actually removed.
2637         client.removeFromChangelists(paths, Depth.infinity, changelists);
2638         clCallback.clear();
2639         client.getChangelists(thisTest.getWCPath(), changelists,
2640                               Depth.infinity, clCallback);
2641         assertTrue(clCallback.isEmpty());
2642     }
2643 
testGetAllChangelists()2644     public void testGetAllChangelists() throws Throwable
2645     {
2646         OneTest thisTest = new OneTest();
2647         final String cl1 = "changelist_one";
2648         final String cl2 = "changelist_too";
2649         MyChangelistCallback clCallback = new MyChangelistCallback();
2650 
2651         String path = fileToSVNPath(new File(thisTest.getWCPath(), "iota"),
2652                                     true);
2653         Set<String> paths = new HashSet<String>(1);
2654         paths.add(path);
2655         client.addToChangelist(paths, cl1, Depth.infinity, null);
2656         paths.remove(path);
2657 
2658         path = fileToSVNPath(new File(thisTest.getWCPath(), "A/B/lambda"),
2659                              true);
2660         paths.add(path);
2661         client.addToChangelist(paths, cl2, Depth.infinity, null);
2662 
2663         client.getChangelists(thisTest.getWCPath(), null,
2664                               Depth.infinity, clCallback);
2665         Collection<String> changelists = clCallback.getChangelists();
2666         assertEquals(2, changelists.size());
2667         assertTrue("Contains " + cl1, changelists.contains(cl1));
2668         assertTrue("Contains " + cl2, changelists.contains(cl2));
2669     }
2670 
2671     /**
2672      * Helper method for testing mergeinfo retrieval.  Assumes
2673      * that <code>targetPath</code> has both merge history and
2674      * available merges.
2675      * @param expectedMergedStart The expected start revision from the
2676      * merge history for <code>mergeSrc</code>.
2677      * @param expectedMergedEnd The expected end revision from the
2678      * merge history for <code>mergeSrc</code>.
2679      * @param expectedAvailableStart The expected start available revision
2680      * from the merge history for <code>mergeSrc</code>.  Zero if no need
2681      * to test the available range.
2682      * @param expectedAvailableEnd The expected end available revision
2683      * from the merge history for <code>mergeSrc</code>.
2684      * @param targetPath The path for which to acquire mergeinfo.
2685      * @param mergeSrc The URL from which to consider merges.
2686      */
acquireMergeinfoAndAssertEquals(long expectedMergeStart, long expectedMergeEnd, long expectedAvailableStart, long expectedAvailableEnd, String targetPath, String mergeSrc)2687     private void acquireMergeinfoAndAssertEquals(long expectedMergeStart,
2688                                                  long expectedMergeEnd,
2689                                                  long expectedAvailableStart,
2690                                                  long expectedAvailableEnd,
2691                                                  String targetPath,
2692                                                  String mergeSrc)
2693         throws SubversionException
2694     {
2695         // Verify expected merge history.
2696         Mergeinfo mergeInfo = client.getMergeinfo(targetPath, Revision.HEAD);
2697         assertNotNull("Missing merge info on '" + targetPath + '\'',
2698                       mergeInfo);
2699         List<RevisionRange> ranges = mergeInfo.getRevisions(mergeSrc);
2700         assertTrue("Missing merge info for source '" + mergeSrc + "' on '" +
2701                    targetPath + '\'', ranges != null && !ranges.isEmpty());
2702         RevisionRange range = ranges.get(0);
2703         String expectedMergedRevs = expectedMergeStart + "-" + expectedMergeEnd;
2704         assertEquals("Unexpected first merged revision range for '" +
2705                      mergeSrc + "' on '" + targetPath + '\'',
2706                      expectedMergedRevs, range.toString());
2707 
2708         // Verify expected available merges.
2709         if (expectedAvailableStart > 0)
2710         {
2711             long[] availableRevs =
2712                     getMergeinfoRevisions(Mergeinfo.LogKind.eligible, targetPath,
2713                                           Revision.HEAD, mergeSrc,
2714                                           Revision.HEAD);
2715             assertNotNull("Missing eligible merge info on '"+targetPath + '\'',
2716                           availableRevs);
2717             assertExpectedMergeRange(expectedAvailableStart,
2718                                      expectedAvailableEnd, availableRevs);
2719             }
2720     }
2721 
2722     /**
2723      * Calls the API to get mergeinfo revisions and returns
2724      * the revision numbers in a sorted array, or null if there
2725      * are no revisions to return.
2726      * @since 1.5
2727      */
getMergeinfoRevisions(Mergeinfo.LogKind kind, String pathOrUrl, Revision pegRevision, String mergeSourceUrl, Revision srcPegRevision)2728     private long[] getMergeinfoRevisions(Mergeinfo.LogKind kind,
2729                                          String pathOrUrl,
2730                                          Revision pegRevision,
2731                                          String mergeSourceUrl,
2732                                          Revision srcPegRevision)
2733         throws SubversionException
2734     {
2735         final List<Long> revList = new ArrayList<Long>();
2736 
2737         client.getMergeinfoLog(kind, pathOrUrl, pegRevision, mergeSourceUrl,
2738             srcPegRevision, false, Depth.empty, null,
2739             new LogMessageCallback () {
2740                 public void singleMessage(Set<ChangePath> changedPaths,
2741                     long revision, Map<String, byte[]> revprops,
2742                     boolean hasChildren)
2743                 { revList.add(Long.valueOf(revision)); }
2744             });
2745 
2746         long[] revisions = new long[revList.size()];
2747         int i = 0;
2748         for (Long revision : revList)
2749         {
2750             revisions[i] = revision.longValue();
2751             i++;
2752         }
2753         return revisions;
2754     }
2755 
2756     /**
2757      * Append the text <code>toAppend</code> to the WC file at
2758      * <code>path</code>, and update the expected WC state
2759      * accordingly.
2760      *
2761      * @param thisTest The test whose expected WC to tweak.
2762      * @param path The working copy-relative path to change.
2763      * @param toAppend The text to append to <code>path</code>.
2764      * @param rev The expected revision number for thisTest's WC.
2765      * @return The file created during the setup.
2766      * @since 1.5
2767      */
appendText(OneTest thisTest, String path, String toAppend, int rev)2768     private File appendText(OneTest thisTest, String path, String toAppend,
2769                             int rev)
2770         throws FileNotFoundException
2771     {
2772         File f = new File(thisTest.getWorkingCopy(), path);
2773         PrintWriter writer = new PrintWriter(new FileOutputStream(f, true));
2774         writer.print(toAppend);
2775         writer.close();
2776         if (rev > 0)
2777         {
2778             WC wc = thisTest.getWc();
2779             wc.setItemWorkingCopyRevision(path, rev);
2780             wc.setItemContent(path, wc.getItemContent(path) + toAppend);
2781         }
2782         addExpectedCommitItem(thisTest.getWCPath(),
2783                              thisTest.getUrl().toString(), path,
2784                               NodeKind.file, CommitItemStateFlags.TextMods);
2785         return f;
2786     }
2787 
2788     /**
2789      * Test the basic functionality of SVNClient.merge().
2790      * @throws Throwable
2791      * @since 1.2
2792      */
testBasicMerge()2793     public void testBasicMerge() throws Throwable
2794     {
2795         OneTest thisTest = setupAndPerformMerge();
2796 
2797         // Verify that there are now potential merge sources.
2798         Set<String> suggestedSrcs =
2799             client.suggestMergeSources(thisTest.getWCPath() + "/branches/A",
2800                                        Revision.WORKING);
2801         assertNotNull(suggestedSrcs);
2802         assertEquals(1, suggestedSrcs.size());
2803 
2804         // Test that getMergeinfo() returns null.
2805         assertNull(client.getMergeinfo(new File(thisTest.getWCPath(), "A")
2806                                        .toString(), Revision.HEAD));
2807 
2808         // Merge and commit some changes (r4).
2809         appendText(thisTest, "A/mu", "xxx", 4);
2810         appendText(thisTest, "A/D/G/rho", "yyy", 4);
2811         checkCommitRevision(thisTest, "wrong revision number from commit", 4,
2812                             thisTest.getWCPathSet(), "log msg", Depth.infinity,
2813                             false, false, null, null);
2814 
2815         // Add a "begin merge" notification handler.
2816         final Revision[] actualRange = new Revision[2];
2817         ClientNotifyCallback notify = new ClientNotifyCallback()
2818         {
2819             public void onNotify(ClientNotifyInformation info)
2820             {
2821                 if (info.getAction() == ClientNotifyInformation.Action.merge_begin)
2822                 {
2823                     RevisionRange r = info.getMergeRange();
2824                     actualRange[0] = r.getFromRevision();
2825                     actualRange[1] = r.getToRevision();
2826                 }
2827             }
2828         };
2829         client.notification2(notify);
2830 
2831         // merge changes in A to branches/A
2832         String branchPath = thisTest.getWCPath() + "/branches/A";
2833         String modUrl = thisTest.getUrl() + "/A";
2834         // test --dry-run
2835         client.merge(modUrl, new Revision.Number(2), modUrl, Revision.HEAD,
2836                      branchPath, false, Depth.infinity, false, true, false);
2837         assertEquals("Notification of beginning of merge reported incorrect " +
2838                      "start revision", new Revision.Number(2), actualRange[0]);
2839         assertEquals("Notification of beginning of merge reported incorrect " +
2840                      "end revision", new Revision.Number(4), actualRange[1]);
2841 
2842         // now do the real merge
2843         client.merge(modUrl, new Revision.Number(2), modUrl, Revision.HEAD,
2844                      branchPath, false, Depth.infinity, false, false, false);
2845         assertEquals("Notification of beginning of merge reported incorrect " +
2846                      "start revision", new Revision.Number(2), actualRange[0]);
2847         assertEquals("Notification of beginning of merge reported incorrect " +
2848                      "end revision", new Revision.Number(4), actualRange[1]);
2849 
2850         // commit the changes so that we can verify merge
2851         addExpectedCommitItem(thisTest.getWCPath(), thisTest.getUrl().toString(),
2852                               "branches/A", NodeKind.dir,
2853                               CommitItemStateFlags.PropMods);
2854         addExpectedCommitItem(thisTest.getWCPath(), thisTest.getUrl().toString(),
2855                               "branches/A/mu", NodeKind.file,
2856                               CommitItemStateFlags.TextMods);
2857         addExpectedCommitItem(thisTest.getWCPath(), thisTest.getUrl().toString(),
2858                               "branches/A/D/G/rho", NodeKind.file,
2859                               CommitItemStateFlags.TextMods);
2860         checkCommitRevision(thisTest, "wrong revision number from commit", 5,
2861                             thisTest.getWCPathSet(), "log msg", Depth.infinity,
2862                             false, false, null, null);
2863 
2864         // Merge and commit some more changes (r6).
2865         appendText(thisTest, "A/mu", "xxxr6", 6);
2866         appendText(thisTest, "A/D/G/rho", "yyyr6", 6);
2867         checkCommitRevision(thisTest, "wrong revision number from commit", 6,
2868                             thisTest.getWCPathSet(), "log msg", Depth.infinity,
2869                             false, false, null, null);
2870 
2871         // Test retrieval of mergeinfo from a WC path.
2872         String targetPath =
2873             new File(thisTest.getWCPath(), "branches/A/mu").getPath();
2874         final String mergeSrc = thisTest.getUrl() + "/A/mu";
2875         acquireMergeinfoAndAssertEquals(2, 4, 6, 6, targetPath, mergeSrc);
2876 
2877         // Test retrieval of mergeinfo from the repository.
2878         targetPath = thisTest.getUrl() + "/branches/A/mu";
2879         acquireMergeinfoAndAssertEquals(2, 4, 6, 6, targetPath, mergeSrc);
2880     }
2881 
2882     /**
2883      * Test merge with automatic source and revision determination
2884      * (e.g. 'svn merge -g').
2885      * @throws Throwable
2886      * @since 1.5
2887      */
testMergeUsingHistory()2888     public void testMergeUsingHistory() throws Throwable
2889     {
2890         OneTest thisTest = setupAndPerformMerge();
2891 
2892         // Test that getMergeinfo() returns null.
2893         assertNull(client.getMergeinfo(new File(thisTest.getWCPath(), "A")
2894                                        .toString(), Revision.HEAD));
2895 
2896         // Merge and commit some changes (r4).
2897         appendText(thisTest, "A/mu", "xxx", 4);
2898         checkCommitRevision(thisTest, "wrong revision number from commit", 4,
2899                             thisTest.getWCPathSet(), "log msg", Depth.infinity,
2900                             false, false, null, null);
2901 
2902         String branchPath = thisTest.getWCPath() + "/branches/A";
2903         String modUrl = thisTest.getUrl() + "/A";
2904         Revision unspec = new Revision(Revision.Kind.unspecified);
2905         List<RevisionRange> ranges = new ArrayList<RevisionRange>(1);
2906         ranges.add(new RevisionRange(unspec, unspec));
2907         client.merge(modUrl, Revision.HEAD, ranges,
2908                      branchPath, true, Depth.infinity, false, false, false);
2909 
2910         // commit the changes so that we can verify merge
2911         addExpectedCommitItem(thisTest.getWCPath(), thisTest.getUrl().toString(),
2912                               "branches/A", NodeKind.dir,
2913                               CommitItemStateFlags.PropMods);
2914         addExpectedCommitItem(thisTest.getWCPath(), thisTest.getUrl().toString(),
2915                               "branches/A/mu", NodeKind.file,
2916                               CommitItemStateFlags.TextMods);
2917         checkCommitRevision(thisTest, "wrong revision number from commit", 5,
2918                             thisTest.getWCPathSet(), "log msg", Depth.infinity,
2919                             false, false, null, null);
2920     }
2921 
2922     /**
2923      * Test merge with automatic source and revision determination
2924      * (e.g. 'svn merge -g) with implied revision range.
2925      * @throws Throwable
2926      * @since 1.8
2927      */
testMergeUsingHistoryImpliedRange()2928     public void testMergeUsingHistoryImpliedRange() throws Throwable
2929     {
2930         OneTest thisTest = setupAndPerformMerge();
2931 
2932         // Test that getMergeinfo() returns null.
2933         assertNull(client.getMergeinfo(new File(thisTest.getWCPath(), "A")
2934                                        .toString(), Revision.HEAD));
2935 
2936         // Merge and commit some changes (r4).
2937         appendText(thisTest, "A/mu", "xxx", 4);
2938         checkCommitRevision(thisTest, "wrong revision number from commit", 4,
2939                             thisTest.getWCPathSet(), "log msg", Depth.infinity,
2940                             false, false, null, null);
2941 
2942         String branchPath = thisTest.getWCPath() + "/branches/A";
2943         String modUrl = thisTest.getUrl() + "/A";
2944         client.merge(modUrl, Revision.HEAD, null,
2945                      branchPath, true, Depth.infinity, false, false, false);
2946 
2947         // commit the changes so that we can verify merge
2948         addExpectedCommitItem(thisTest.getWCPath(), thisTest.getUrl().toString(),
2949                               "branches/A", NodeKind.dir,
2950                               CommitItemStateFlags.PropMods);
2951         addExpectedCommitItem(thisTest.getWCPath(), thisTest.getUrl().toString(),
2952                               "branches/A/mu", NodeKind.file,
2953                               CommitItemStateFlags.TextMods);
2954         checkCommitRevision(thisTest, "wrong revision number from commit", 5,
2955                             thisTest.getWCPathSet(), "log msg", Depth.infinity,
2956                             false, false, null, null);
2957     }
2958 
2959     /**
2960      * Test reintegrating a branch with trunk
2961      * (e.g. 'svn merge --reintegrate').
2962      * @throws Throwable
2963      * @since 1.5
2964      */
2965     @SuppressWarnings("deprecation")
testMergeReintegrate()2966     public void testMergeReintegrate() throws Throwable
2967     {
2968         OneTest thisTest = setupAndPerformMerge();
2969 
2970         // Test that getMergeinfo() returns null.
2971         assertNull(client.getMergeinfo(new File(thisTest.getWCPath(), "A")
2972                                        .toString(), Revision.HEAD));
2973 
2974         // Merge and commit some changes to main (r4).
2975         appendText(thisTest, "A/mu", "xxx", 4);
2976         checkCommitRevision(thisTest,
2977                             "wrong revision number from main commit", 4,
2978                             thisTest.getWCPathSet(), "log msg", Depth.infinity,
2979                             false, false, null, null);
2980         // Merge and commit some changes to branch (r5).
2981         appendText(thisTest, "branches/A/D/G/rho", "yyy", -1);
2982         checkCommitRevision(thisTest,
2983                             "wrong revision number from branch commit", 5,
2984                             thisTest.getWCPathSet(), "log msg", Depth.infinity,
2985                             false, false, null, null);
2986 
2987         // update the branch WC (to r5) before merge
2988         update(thisTest, "/branches");
2989 
2990         String branchPath = thisTest.getWCPath() + "/branches/A";
2991         String modUrl = thisTest.getUrl() + "/A";
2992         Revision unspec = new Revision(Revision.Kind.unspecified);
2993         List<RevisionRange> ranges = new ArrayList<RevisionRange>(1);
2994         ranges.add(new RevisionRange(unspec, unspec));
2995         client.merge(modUrl, Revision.HEAD, ranges,
2996                      branchPath, true, Depth.infinity, false, false, false);
2997 
2998         // commit the changes so that we can verify merge
2999         addExpectedCommitItem(thisTest.getWCPath(), thisTest.getUrl().toString(),
3000                               "branches/A", NodeKind.dir,
3001                               CommitItemStateFlags.PropMods);
3002         addExpectedCommitItem(thisTest.getWCPath(), thisTest.getUrl().toString(),
3003                               "branches/A/mu", NodeKind.file,
3004                               CommitItemStateFlags.TextMods);
3005         checkCommitRevision(thisTest, "wrong revision number from commit", 6,
3006                             thisTest.getWCPathSet(), "log msg", Depth.infinity,
3007                             false, false, null, null);
3008 
3009         // now we --reintegrate the branch with main
3010         String branchUrl = thisTest.getUrl() + "/branches/A";
3011         try
3012         {
3013             client.mergeReintegrate(branchUrl, Revision.HEAD,
3014                                     thisTest.getWCPath() + "/A", false);
3015             fail("reintegrate merged into a mixed-revision WC");
3016         }
3017         catch(ClientException e)
3018         {
3019             // update the WC (to r6) and try again
3020             update(thisTest);
3021             client.mergeReintegrate(branchUrl, Revision.HEAD,
3022                                     thisTest.getWCPath() + "/A", false);
3023         }
3024         // commit the changes so that we can verify merge
3025         addExpectedCommitItem(thisTest.getWCPath(),
3026                              thisTest.getUrl().toString(), "A", NodeKind.dir,
3027                               CommitItemStateFlags.PropMods);
3028         addExpectedCommitItem(thisTest.getWCPath(),
3029                              thisTest.getUrl().toString(), "A/D/G/rho",
3030                              NodeKind.file, CommitItemStateFlags.TextMods);
3031         checkCommitRevision(thisTest, "wrong revision number from commit", 7,
3032                             thisTest.getWCPathSet(), "log msg", Depth.infinity,
3033                             false, false, null, null);
3034 
3035     }
3036 
3037 
3038     /**
3039      * Test reintegrating a branch with trunk, using automatic reintegrate.
3040      */
testMergeAutoReintegrate()3041     public void testMergeAutoReintegrate() throws Throwable
3042     {
3043         OneTest thisTest = setupAndPerformMerge();
3044 
3045         // Test that getMergeinfo() returns null.
3046         assertNull(client.getMergeinfo(new File(thisTest.getWCPath(), "A")
3047                                        .toString(), Revision.HEAD));
3048 
3049         // Merge and commit some changes to main (r4).
3050         appendText(thisTest, "A/mu", "xxx", 4);
3051         checkCommitRevision(thisTest,
3052                             "wrong revision number from main commit", 4,
3053                             thisTest.getWCPathSet(), "log msg", Depth.infinity,
3054                             false, false, null, null);
3055         // Merge and commit some changes to branch (r5).
3056         appendText(thisTest, "branches/A/D/G/rho", "yyy", -1);
3057         checkCommitRevision(thisTest,
3058                             "wrong revision number from branch commit", 5,
3059                             thisTest.getWCPathSet(), "log msg", Depth.infinity,
3060                             false, false, null, null);
3061 
3062         // update the branch WC (to r5) before merge
3063         update(thisTest, "/branches");
3064 
3065         String branchPath = thisTest.getWCPath() + "/branches/A";
3066         String modUrl = thisTest.getUrl() + "/A";
3067         Revision unspec = new Revision(Revision.Kind.unspecified);
3068         List<RevisionRange> ranges = new ArrayList<RevisionRange>(1);
3069         ranges.add(new RevisionRange(unspec, unspec));
3070         client.merge(modUrl, Revision.HEAD, ranges,
3071                      branchPath, true, Depth.infinity, false, false, false);
3072 
3073         // commit the changes so that we can verify merge
3074         addExpectedCommitItem(thisTest.getWCPath(), thisTest.getUrl().toString(),
3075                               "branches/A", NodeKind.dir,
3076                               CommitItemStateFlags.PropMods);
3077         addExpectedCommitItem(thisTest.getWCPath(), thisTest.getUrl().toString(),
3078                               "branches/A/mu", NodeKind.file,
3079                               CommitItemStateFlags.TextMods);
3080         checkCommitRevision(thisTest, "wrong revision number from commit", 6,
3081                             thisTest.getWCPathSet(), "log msg", Depth.infinity,
3082                             false, false, null, null);
3083 
3084         // now we reintegrate the branch with main
3085         String branchUrl = thisTest.getUrl() + "/branches/A";
3086         client.merge(branchUrl, Revision.HEAD, null,
3087                      thisTest.getWCPath() + "/A", false,
3088                      Depth.unknown, false, false, false, false);
3089 
3090         // make the working copy up-to-date, so that mergeinfo can be committed
3091         update(thisTest);
3092         // commit the changes so that we can verify merge
3093         addExpectedCommitItem(thisTest.getWCPath(),
3094                              thisTest.getUrl().toString(), "A", NodeKind.dir,
3095                               CommitItemStateFlags.PropMods);
3096         addExpectedCommitItem(thisTest.getWCPath(),
3097                              thisTest.getUrl().toString(), "A/D/G/rho",
3098                              NodeKind.file, CommitItemStateFlags.TextMods);
3099         checkCommitRevision(thisTest, "wrong revision number from commit", 7,
3100                             thisTest.getWCPathSet(), "log msg", Depth.infinity,
3101                             false, false, null, null);
3102 
3103     }
3104 
3105     /**
3106      * Test automatic merge conflict resolution.
3107      * @throws Throwable
3108      * @since 1.5
3109      */
testMergeConflictResolution()3110     public void testMergeConflictResolution() throws Throwable
3111     {
3112         // Add a conflict resolution callback which always chooses the
3113         // user's version of a conflicted file.
3114         client.setConflictResolver(new ConflictResolverCallback()
3115             {
3116                 public ConflictResult resolve(ConflictDescriptor descrip)
3117                 {
3118                     return new ConflictResult(ConflictResult.Choice.chooseTheirsConflict,
3119                                               null);
3120                 }
3121             });
3122 
3123         OneTest thisTest = new OneTest();
3124         String originalContents = thisTest.getWc().getItemContent("A/mu");
3125         String expectedContents = originalContents + "xxx";
3126 
3127         // Merge and commit a change (r2).
3128         File mu = appendText(thisTest, "A/mu", "xxx", 2);
3129         checkCommitRevision(thisTest, "wrong revision number from commit", 2,
3130                             thisTest.getWCPathSet(), "log msg", Depth.infinity,
3131                             false, false, null, null);
3132 
3133         // Backdate the WC to the previous revision (r1).
3134         client.update(thisTest.getWCPathSet(), Revision.getInstance(1),
3135                       Depth.unknown, false, false, false, false);
3136 
3137         // Prep for a merge conflict by changing A/mu in a different
3138         // way.
3139         mu = appendText(thisTest, "A/mu", "yyy", 1);
3140 
3141         // Merge in the previous changes to A/mu (from r2).
3142         List<RevisionRange> ranges = new ArrayList<RevisionRange>(1);
3143         ranges.add(new RevisionRange(new Revision.Number(1),
3144                                      new Revision.Number(2)));
3145         client.merge(thisTest.getUrl().toString(), Revision.HEAD, ranges,
3146                      thisTest.getWCPath(), false, Depth.infinity, false,
3147                      false, false);
3148 
3149         assertFileContentsEquals("Unexpected conflict resolution",
3150                                  expectedContents, mu);
3151     }
3152 
3153     /**
3154      * Test merge --record-only
3155      * @throws Throwable
3156      * @since 1.5
3157      */
testRecordOnlyMerge()3158     public void testRecordOnlyMerge() throws Throwable
3159     {
3160         OneTest thisTest = setupAndPerformMerge();
3161 
3162         // Verify that there are now potential merge sources.
3163         Set<String> suggestedSrcs =
3164             client.suggestMergeSources(thisTest.getWCPath() + "/branches/A",
3165                                        Revision.WORKING);
3166         assertNotNull(suggestedSrcs);
3167         assertEquals(1, suggestedSrcs.size());
3168 
3169         // Test that getMergeinfo() returns null.
3170         assertNull(client.getMergeinfo(new File(thisTest.getWCPath(), "A")
3171                                        .toString(), Revision.HEAD));
3172 
3173         // Merge and commit some changes (r4).
3174         appendText(thisTest, "A/mu", "xxx", 4);
3175         appendText(thisTest, "A/D/G/rho", "yyy", 4);
3176         checkCommitRevision(thisTest, "wrong revision number from commit", 4,
3177                             thisTest.getWCPathSet(), "log msg", Depth.infinity,
3178                             false, false, null, null);
3179 
3180         // --record-only merge changes in A to branches/A
3181         String branchPath = thisTest.getWCPath() + "/branches/A";
3182         String modUrl = thisTest.getUrl() + "/A";
3183 
3184         List<RevisionRange> ranges = new ArrayList<RevisionRange>(1);
3185         ranges.add(new RevisionRange(new Revision.Number(2),
3186                                      new Revision.Number(4)));
3187         client.merge(modUrl, Revision.HEAD, ranges,
3188                      branchPath, true, Depth.infinity, false, false, true);
3189 
3190         // commit the changes so that we can verify merge
3191         addExpectedCommitItem(thisTest.getWCPath(), thisTest.getUrl().toString(),
3192                               "branches/A", NodeKind.dir,
3193                               CommitItemStateFlags.PropMods);
3194         checkCommitRevision(thisTest, "wrong revision number from commit", 5,
3195                             thisTest.getWCPathSet(), "log msg", Depth.infinity,
3196                             false, false, null, null);
3197 
3198         // Test retrieval of mergeinfo from a WC path.
3199         String targetPath =
3200             new File(thisTest.getWCPath(), "branches/A").getPath();
3201         final String mergeSrc = thisTest.getUrl() + "/A";
3202         acquireMergeinfoAndAssertEquals(2, 4, 0, 0, targetPath, mergeSrc);
3203     }
3204 
3205     /**
3206      * Setup a test with a WC.  In the repository, create a
3207      * "/branches" directory, with a branch of "/A" underneath it.
3208      * Update the WC to reflect these modifications.
3209      * @return This test.
3210      */
setupAndPerformMerge()3211     private OneTest setupAndPerformMerge()
3212         throws Exception
3213     {
3214         OneTest thisTest = new OneTest();
3215 
3216         // Verify that there are initially no potential merge sources.
3217         Set<String> suggestedSrcs =
3218             client.suggestMergeSources(thisTest.getWCPath(),
3219                                        Revision.WORKING);
3220         assertNotNull(suggestedSrcs);
3221         assertEquals(0, suggestedSrcs.size());
3222 
3223         // create branches directory in the repository (r2)
3224         addExpectedCommitItem(null, thisTest.getUrl().toString(), "branches",
3225                               NodeKind.none, CommitItemStateFlags.Add);
3226         Set<String> paths = new HashSet<String>(1);
3227         paths.add(thisTest.getUrl() + "/branches");
3228         client.mkdir(paths, false, null, new ConstMsg("log_msg"), null);
3229 
3230         // copy A to branches (r3)
3231         addExpectedCommitItem(null, thisTest.getUrl().toString(), "branches/A",
3232                               NodeKind.none, CommitItemStateFlags.Add);
3233         List<CopySource> srcs = new ArrayList<CopySource>(1);
3234         srcs.add(new CopySource(thisTest.getUrl() + "/A", Revision.HEAD,
3235                                 Revision.HEAD));
3236         client.copy(srcs, thisTest.getUrl() + "/branches/A",
3237                     true, false, false, false, false, null, null,
3238                     new ConstMsg("create A branch"), null);
3239 
3240         // update the WC (to r3) so that it has the branches folder
3241         update(thisTest);
3242 
3243         return thisTest;
3244     }
3245 
3246     /**
3247      * Test the patch API.  This doesn't yet test the results, it only ensures
3248      * that execution goes down to the C layer and back.
3249      * @throws Throwable
3250      */
testPatch()3251     public void testPatch() throws SubversionException, IOException
3252     {
3253         OneTest thisTest = new OneTest(true);
3254         File patchInput = new File(super.localTmp, thisTest.testName);
3255         final String NL = System.getProperty("line.separator");
3256 
3257         final String patchText = "Index: iota" + NL +
3258             "===================================================================" + NL +
3259             "--- iota\t(revision 1)" + NL +
3260             "+++ iota\t(working copy)" + NL +
3261             "@@ -1 +1,2 @@" + NL +
3262             " This is the file 'iota'." + NL +
3263             "+No, this is *really* the file 'iota'." + NL;
3264 
3265         PrintWriter writer = new PrintWriter(new FileOutputStream(patchInput));
3266         writer.print(patchText);
3267         writer.flush();
3268         writer.close();
3269 
3270         client.patch(patchInput.getAbsolutePath(),
3271                      thisTest.getWCPath().replace('\\', '/'), false, 0,
3272                      false, true, true,
3273                      new PatchCallback() {
3274                          public boolean singlePatch(String pathFromPatchfile,
3275                                                     String patchPath,
3276                                                     String rejectPath) {
3277                              // Do nothing, right now.
3278                             return false;
3279                          }
3280         });
3281     }
3282 
3283     /**
3284      * Test the {@link ISVNClient.diff()} APIs.
3285      * @since 1.5
3286      */
testDiff()3287     public void testDiff()
3288         throws SubversionException, IOException
3289     {
3290         OneTest thisTest = new OneTest(true);
3291         File diffOutput = new File(super.localTmp, thisTest.testName);
3292         final String NL = System.getProperty("line.separator");
3293         final String sepLine =
3294             "===================================================================" + NL;
3295         final String underSepLine =
3296             "___________________________________________________________________" + NL;
3297         final String expectedDiffBody =
3298             "@@ -1 +1 @@" + NL +
3299             "-This is the file 'iota'." + NL +
3300             "\\ No newline at end of file" + NL +
3301             "+This is the file 'mu'." + NL +
3302             "\\ No newline at end of file" + NL;
3303 
3304         final String iotaPath = thisTest.getWCPath().replace('\\', '/') + "/iota";
3305         final String wcPath = fileToSVNPath(new File(thisTest.getWCPath()),
3306                 false);
3307 
3308         // make edits to iota
3309         PrintWriter writer = new PrintWriter(new FileOutputStream(iotaPath));
3310         writer.print("This is the file 'mu'.");
3311         writer.flush();
3312         writer.close();
3313 
3314         /*
3315          * This test does tests with and without svn:eol-style set to native
3316          * We will first run all of the tests where this does not matter so
3317          * that they are not run twice.
3318          */
3319 
3320         // Two-path diff of URLs.
3321         String expectedDiffOutput = "Index: iota" + NL + sepLine +
3322             "--- iota\t(.../iota)\t(revision 1)" + NL +
3323             "+++ iota\t(.../A/mu)\t(revision 1)" + NL +
3324             expectedDiffBody;
3325         client.diff(thisTest.getUrl() + "/iota", Revision.HEAD,
3326                     thisTest.getUrl() + "/A/mu", Revision.HEAD,
3327                     null, diffOutput.getPath(), Depth.files, null, true, true,
3328                     false, false);
3329         assertFileContentsEquals("Unexpected diff output in file '" +
3330                                  diffOutput.getPath() + '\'',
3331                                  expectedDiffOutput, diffOutput);
3332 
3333         // Test relativeToDir fails with urls. */
3334         try
3335         {
3336             client.diff(thisTest.getUrl().toString() + "/iota", Revision.HEAD,
3337                         thisTest.getUrl().toString() + "/A/mu", Revision.HEAD,
3338                         thisTest.getUrl().toString(), diffOutput.getPath(),
3339                         Depth.infinity, null, true, true, false, false);
3340 
3341             fail("This test should fail because the relativeToDir parameter " +
3342                  "does not work with URLs");
3343         }
3344         catch (Exception ignored)
3345         {
3346         }
3347 
3348         /* Testing the expected failure when relativeToDir is not a parent
3349            path of the target. */
3350         try
3351         {
3352             client.diff(iotaPath, Revision.BASE, iotaPath, Revision.WORKING,
3353                         "/non/existent/path", diffOutput.getPath(),
3354                         Depth.infinity, null, true, true, false, false);
3355 
3356             fail("This test should fail because iotaPath is not a child of " +
3357                  "the relativeToDir parameter");
3358         }
3359         catch (Exception ignored)
3360         {
3361         }
3362 
3363         // Test diff with a relative path on a directory with prop
3364         // changes.
3365         String aPath = fileToSVNPath(new File(thisTest.getWCPath() + "/A"),
3366                                      false);
3367 
3368         expectedDiffOutput = "Index: A" + NL + sepLine +
3369             "--- A\t(revision 1)" + NL +
3370             "+++ A\t(working copy)" + NL +
3371             NL + "Property changes on: A" + NL +
3372             underSepLine +
3373             "Added: testprop" + NL +
3374             "## -0,0 +1 ##" + NL +
3375             "+Test property value." + NL;
3376 
3377         setprop(aPath, "testprop", "Test property value." + NL);
3378         client.diff(aPath, Revision.BASE, aPath, Revision.WORKING, wcPath,
3379                     diffOutput.getPath(), Depth.infinity, null, true, true,
3380                     false, false);
3381         assertFileContentsEquals("Unexpected diff output in file '" +
3382                                  diffOutput.getPath() + '\'',
3383                                  expectedDiffOutput, diffOutput);
3384 
3385         // Test diff where relativeToDir and path are the same.
3386         expectedDiffOutput = "Index: ." + NL + sepLine +
3387             "--- .\t(revision 1)" + NL +
3388             "+++ .\t(working copy)" + NL +
3389             NL + "Property changes on: ." + NL +
3390             underSepLine +
3391             "Added: testprop" + NL +
3392             "## -0,0 +1 ##" + NL +
3393             "+Test property value." + NL;
3394 
3395         setprop(aPath, "testprop", "Test property value." + NL);
3396         client.diff(aPath, Revision.BASE, aPath, Revision.WORKING, aPath,
3397                     diffOutput.getPath(), Depth.infinity, null, true, true,
3398                     false, false);
3399         assertFileContentsEquals("Unexpected diff output in file '" +
3400                                  diffOutput.getPath() + '\'',
3401                                  expectedDiffOutput, diffOutput);
3402 
3403 
3404         /*
3405          * The rest of these tests are run twice.  The first time
3406          * without svn:eol-style set and the second time with the
3407          * property set to native.  This is tracked by the int named
3408          * operativeRevision.  It will have a value = 2 after the
3409          * commit which sets the property
3410          */
3411 
3412         for (int operativeRevision = 1; operativeRevision < 3; operativeRevision++)
3413          {
3414                 String revisionPrefix = "While processing operativeRevison=" + operativeRevision + ". ";
3415                 String assertPrefix = revisionPrefix + "Unexpected diff output in file '";
3416 
3417                 // Undo previous edits to working copy
3418                 client.revert(wcPath, Depth.infinity, null);
3419 
3420                 if (operativeRevision == 2) {
3421                     // Set svn:eol-style=native on iota
3422                     setprop(iotaPath, "svn:eol-style", "native");
3423                     Set<String> paths = new HashSet<String>(1);
3424                     paths.add(iotaPath);
3425                     addExpectedCommitItem(thisTest.getWCPath(),
3426                             thisTest.getUrl().toString(), "iota",NodeKind.file,
3427                             CommitItemStateFlags.PropMods);
3428                     client.commit(paths, Depth.empty, false, false, null, null,
3429                                   new ConstMsg("Set svn:eol-style to native"),
3430                                   null);
3431                 }
3432 
3433                 // make edits to iota and set expected output.
3434                 writer = new PrintWriter(new FileOutputStream(iotaPath));
3435                 writer.print("This is the file 'mu'.");
3436                 writer.flush();
3437                 writer.close();
3438                 expectedDiffOutput = "Index: " + iotaPath + NL + sepLine +
3439                     "--- " + iotaPath + "\t(revision " + operativeRevision + ")" + NL +
3440                     "+++ " + iotaPath + "\t(working copy)" + NL +
3441                     expectedDiffBody;
3442 
3443                 try
3444                 {
3445                     // Two-path diff of WC paths.
3446                     client.diff(iotaPath, Revision.BASE, iotaPath,
3447                                 Revision.WORKING, null, diffOutput.getPath(),
3448                                 Depth.files, null, true, true, false, false);
3449                     assertFileContentsEquals(assertPrefix +
3450                                              diffOutput.getPath() + '\'',
3451                                              expectedDiffOutput, diffOutput);
3452                     diffOutput.delete();
3453                 }
3454                 catch (ClientException e)
3455                 {
3456                     fail(revisionPrefix + e.getMessage());
3457                 }
3458 
3459                 try
3460                 {
3461                     // Peg revision diff of a single file.
3462                     client.diff(thisTest.getUrl() + "/iota", Revision.HEAD,
3463                                 new Revision.Number(operativeRevision),
3464                                 Revision.HEAD, null, diffOutput.getPath(),
3465                                 Depth.files, null, true, true, false, false);
3466                     assertFileContentsEquals(assertPrefix +
3467                                              diffOutput.getPath() + '\'',
3468                                              "", diffOutput);
3469 
3470                     diffOutput.delete();
3471                 }
3472                 catch (ClientException e)
3473                 {
3474                     fail(revisionPrefix + e.getMessage());
3475                 }
3476 
3477                // Test svn diff with a relative path.
3478                 expectedDiffOutput = "Index: iota" + NL + sepLine +
3479                     "--- iota\t(revision " + operativeRevision + ")" + NL +
3480                     "+++ iota\t(working copy)" + NL +
3481                     expectedDiffBody;
3482                 try
3483                 {
3484                     client.diff(iotaPath, Revision.BASE, iotaPath,
3485                                 Revision.WORKING, wcPath, diffOutput.getPath(),
3486                                 Depth.infinity, null, true, true, false,
3487                                 false);
3488                     assertFileContentsEquals(assertPrefix +
3489                                              diffOutput.getPath() + '\'',
3490                                              expectedDiffOutput, diffOutput);
3491                     diffOutput.delete();
3492                 }
3493                 catch (ClientException e)
3494                 {
3495                     fail(revisionPrefix + e.getMessage());
3496                 }
3497 
3498                 try
3499                 {
3500                     // Test svn diff with a relative path and trailing slash.
3501                     client.diff(iotaPath, Revision.BASE, iotaPath,
3502                                 Revision.WORKING, wcPath + "/",
3503                                 diffOutput.getPath(), Depth.infinity, null,
3504                                 true, true, false, false);
3505                     assertFileContentsEquals(assertPrefix +
3506                                              diffOutput.getPath() + '\'',
3507                                              expectedDiffOutput, diffOutput);
3508                     diffOutput.delete();
3509                 }
3510                 catch (ClientException e)
3511                 {
3512                     fail(revisionPrefix + e.getMessage());
3513                 }
3514 
3515             }
3516 
3517     }
3518 
3519     /**
3520      * Test the {@link ISVNClient.diff()} with {@link DiffOptions}.
3521      * @since 1.8
3522      */
testDiffOptions()3523     public void testDiffOptions()
3524         throws SubversionException, IOException
3525     {
3526         OneTest thisTest = new OneTest(true);
3527         File diffOutput = new File(super.localTmp, thisTest.testName);
3528         final String NL = System.getProperty("line.separator");
3529         final String sepLine =
3530             "===================================================================" + NL;
3531         final String underSepLine =
3532             "___________________________________________________________________" + NL;
3533         final String iotaPath = thisTest.getWCPath().replace('\\', '/') + "/iota";
3534         final String wcPath = fileToSVNPath(new File(thisTest.getWCPath()),
3535                 false);
3536         final String expectedDiffHeader =
3537             "Index: iota" + NL + sepLine +
3538             "--- iota\t(revision 1)" + NL +
3539             "+++ iota\t(working copy)" + NL;
3540 
3541         // make edits to iota
3542         PrintWriter writer = new PrintWriter(new FileOutputStream(iotaPath));
3543         writer.print("This is  the  file 'iota'.");
3544         writer.flush();
3545         writer.close();
3546 
3547         try
3548         {
3549             final String expectedDiffOutput = expectedDiffHeader +
3550                 "@@ -1 +1 @@" + NL +
3551                 "-This is the file 'iota'." + NL +
3552                 "\\ No newline at end of file" + NL +
3553                 "+This is  the  file 'iota'." + NL +
3554                 "\\ No newline at end of file" + NL;
3555 
3556             client.diff(iotaPath, Revision.BASE, iotaPath, Revision.WORKING,
3557                         wcPath, new FileOutputStream(diffOutput.getPath()),
3558                         Depth.infinity, null,
3559                         false, false, false, false, false, false, null);
3560             assertFileContentsEquals(
3561                 "Unexpected diff output with no options in file '" +
3562                 diffOutput.getPath() + '\'',
3563                 expectedDiffOutput, diffOutput);
3564             diffOutput.delete();
3565         }
3566         catch (ClientException e)
3567         {
3568             fail(e.getMessage());
3569         }
3570 
3571         try
3572         {
3573             final String expectedDiffOutput = "";
3574 
3575             client.diff(iotaPath, Revision.BASE, iotaPath, Revision.WORKING,
3576                         wcPath, new FileOutputStream(diffOutput.getPath()),
3577                         Depth.infinity, null,
3578                         false, false, false, false, false, false,
3579                         new DiffOptions(DiffOptions.Flag.IgnoreWhitespace));
3580             assertFileContentsEquals(
3581                 "Unexpected diff output with Flag.IgnoreWhitespace in file '" +
3582                 diffOutput.getPath() + '\'',
3583                 expectedDiffOutput, diffOutput);
3584             diffOutput.delete();
3585         }
3586         catch (ClientException e)
3587         {
3588             fail("Using Flag.IgnoreWhitespace: "
3589                   + e.getMessage());
3590         }
3591 
3592         try
3593         {
3594             final String expectedDiffOutput = "";
3595 
3596             client.diff(iotaPath, Revision.BASE, iotaPath, Revision.WORKING,
3597                         wcPath, diffOutput.getPath(), Depth.infinity, null,
3598                         false, false, false, false, false, false,
3599                         new DiffOptions(DiffOptions.Flag.IgnoreSpaceChange));
3600             assertFileContentsEquals(
3601                 "Unexpected diff output with Flag.IgnoreSpaceChange in file '" +
3602                 diffOutput.getPath() + '\'',
3603                 expectedDiffOutput, diffOutput);
3604             diffOutput.delete();
3605         }
3606         catch (ClientException e)
3607         {
3608             fail("Using Flag.IgnoreSpaceChange: "
3609                  + e.getMessage());
3610         }
3611 
3612         // make edits to iota
3613         writer = new PrintWriter(new FileOutputStream(iotaPath));
3614         writer.print("This is  the  file 'io ta'.");
3615         writer.flush();
3616         writer.close();
3617 
3618         try
3619         {
3620             final String expectedDiffOutput = expectedDiffHeader +
3621                 "@@ -1 +1 @@" + NL +
3622                 "-This is the file 'iota'." + NL +
3623                 "\\ No newline at end of file" + NL +
3624                 "+This is  the  file 'io ta'." + NL +
3625                 "\\ No newline at end of file" + NL;
3626 
3627             client.diff(iotaPath, Revision.BASE, iotaPath, Revision.WORKING,
3628                         wcPath, diffOutput.getPath(), Depth.infinity, null,
3629                         false, false, false, false, false, false,
3630                         new DiffOptions(DiffOptions.Flag.IgnoreSpaceChange));
3631             assertFileContentsEquals(
3632                 "Unexpected diff output with Flag.IgnoreSpaceChange in file '" +
3633                 diffOutput.getPath() + '\'',
3634                 expectedDiffOutput, diffOutput);
3635             diffOutput.delete();
3636         }
3637         catch (ClientException e)
3638         {
3639             fail("Using Flag.IgnoreSpaceChange: "
3640                  + e.getMessage());
3641         }
3642     }
3643 
3644 
assertFileContentsEquals(String msg, String expected, File actual)3645     private void assertFileContentsEquals(String msg, String expected,
3646                                           File actual)
3647         throws IOException
3648     {
3649         FileReader reader = new FileReader(actual);
3650         StringBuffer buf = new StringBuffer();
3651         int ch;
3652         while ((ch = reader.read()) != -1)
3653         {
3654             buf.append((char) ch);
3655         }
3656         assertEquals(msg, expected, buf.toString());
3657     }
3658 
3659     /**
3660      * Test the {@link SVNClientInterface.diffSummarize()} API.
3661      * @since 1.5
3662      */
testDiffSummarize()3663     public void testDiffSummarize()
3664         throws SubversionException, IOException
3665     {
3666         OneTest thisTest = new OneTest(false);
3667         DiffSummaries summaries = new DiffSummaries();
3668         // Perform a recursive diff summary, ignoring ancestry.
3669         client.diffSummarize(thisTest.getUrl().toString(), new Revision.Number(0),
3670                              thisTest.getUrl().toString(), Revision.HEAD, Depth.infinity,
3671                              null, false, summaries);
3672         assertExpectedDiffSummaries(summaries);
3673 
3674         summaries.clear();
3675         // Perform a recursive diff summary with a peg revision,
3676         // ignoring ancestry.
3677         client.diffSummarize(thisTest.getUrl().toString(), Revision.HEAD,
3678                              new Revision.Number(0), Revision.HEAD,
3679                              Depth.infinity, null, false, summaries);
3680         assertExpectedDiffSummaries(summaries);
3681     }
3682 
assertExpectedDiffSummaries(DiffSummaries summaries)3683     private void assertExpectedDiffSummaries(DiffSummaries summaries)
3684     {
3685         assertEquals("Wrong number of diff summary descriptors", 20,
3686                      summaries.size());
3687 
3688         // Rigorously inspect one of our DiffSummary notifications.
3689         final String BETA_PATH = "A/B/E/beta";
3690         DiffSummary betaDiff = summaries.get(BETA_PATH);
3691         assertNotNull("No diff summary for " + BETA_PATH, betaDiff);
3692         assertEquals("Incorrect path for " + BETA_PATH, BETA_PATH,
3693                      betaDiff.getPath());
3694         assertTrue("Incorrect diff kind for " + BETA_PATH,
3695                    betaDiff.getDiffKind() == DiffSummary.DiffKind.added);
3696         assertEquals("Incorrect props changed notice for " + BETA_PATH,
3697                      false, betaDiff.propsChanged());
3698         assertEquals("Incorrect node kind for " + BETA_PATH, NodeKind.file,
3699                      betaDiff.getNodeKind());
3700     }
3701 
3702     /**
3703      * test the basic SVNClient.isAdminDirectory functionality
3704      * @throws Throwable
3705      * @since 1.2
3706      */
testBasicIsAdminDirectory()3707     public void testBasicIsAdminDirectory() throws Throwable
3708     {
3709         // build the test setup
3710         OneTest thisTest = new OneTest();
3711         ClientNotifyCallback notify = new ClientNotifyCallback()
3712         {
3713             public void onNotify(ClientNotifyInformation info)
3714             {
3715                 client.isAdminDirectory(".svn");
3716             }
3717         };
3718         client.notification2(notify);
3719         // update the test
3720         assertEquals("wrong revision number from update",
3721                      update(thisTest), 1);
3722     }
3723 
testBasicCancelOperation()3724     public void testBasicCancelOperation() throws Throwable
3725     {
3726         // build the test setup
3727         OneTest thisTest = new OneTest();
3728         ClientNotifyCallback notify = new ClientNotifyCallback()
3729         {
3730             public void onNotify(ClientNotifyInformation info)
3731             {
3732                 try
3733                 {
3734                     client.cancelOperation();
3735                 }
3736                 catch (ClientException e)
3737                 {
3738                     fail(e.getMessage());
3739                 }
3740             }
3741         };
3742         client.notification2(notify);
3743         // update the test to try to cancel an operation
3744         try
3745         {
3746             update(thisTest);
3747             fail("missing exception for canceled operation");
3748         }
3749         catch (ClientException e)
3750         {
3751             // this is expected
3752         }
3753     }
3754 
3755     private static class CountingProgressListener implements ProgressCallback
3756     {
onProgress(ProgressEvent event)3757         public void onProgress(ProgressEvent event)
3758         {
3759             // TODO: Examine the byte counts from "event".
3760             gotProgress = true;
3761         }
3762         public boolean gotProgress = false;
3763     }
3764 
testDataTransferProgressReport()3765     public void testDataTransferProgressReport() throws Throwable
3766     {
3767         // ### FIXME: This isn't working over ra_local, because
3768         // ### ra_local is not invoking the progress callback.
3769         if (SVNTests.rootUrl.startsWith("file://"))
3770             return;
3771 
3772         // build the test setup
3773         OneTest thisTest = new OneTest();
3774         CountingProgressListener listener = new CountingProgressListener();
3775         client.setProgressCallback(listener);
3776 
3777         // Perform an update to exercise the progress notification.
3778         update(thisTest);
3779         if (!listener.gotProgress)
3780             fail("No progress reported");
3781     }
3782 
3783     /**
3784      * Test the basic tree conflict functionality.
3785      * @throws Throwable
3786      */
testTreeConflict()3787     public void testTreeConflict() throws Throwable
3788     {
3789         // build the test setup. Used for the changes
3790         OneTest thisTest = new OneTest();
3791         WC wc = thisTest.getWc();
3792 
3793         // build the backup test setup. That is the one that will be updated
3794         OneTest tcTest = thisTest.copy(".tree-conflict");
3795 
3796 
3797         // Move files from A/B/E to A/B/F.
3798         Set<String> relPaths = new HashSet<String>(1);
3799         relPaths.add("alpha");
3800         Set<String> srcPaths = new HashSet<String>(1);
3801         for (String fileName : relPaths)
3802         {
3803             srcPaths.add(new File(thisTest.getWorkingCopy(),
3804                                    "A/B/E/" + fileName).getPath());
3805 
3806             wc.addItem("A/B/F/" + fileName,
3807                        wc.getItemContent("A/B/E/" + fileName));
3808             wc.setItemWorkingCopyRevision("A/B/F/" + fileName, 2);
3809             addExpectedCommitItem(thisTest.getWCPath(), thisTest.getUrl().toString(),
3810                                   "A/B/F/" + fileName, NodeKind.file,
3811                                   CommitItemStateFlags.Add |
3812                                   CommitItemStateFlags.IsCopy);
3813 
3814             wc.removeItem("A/B/E/" + fileName);
3815             addExpectedCommitItem(thisTest.getWCPath(), thisTest.getUrl().toString(),
3816                                   "A/B/E/" + fileName, NodeKind.file,
3817                                   CommitItemStateFlags.Delete);
3818         }
3819         client.move(srcPaths,
3820                     new File(thisTest.getWorkingCopy(), "A/B/F").getPath(),
3821                     false, true, false, false, false, null, null, null);
3822 
3823         // Commit the changes, and check the state of the WC.
3824         checkCommitRevision(thisTest,
3825                             "Unexpected WC revision number after commit", 2,
3826                             thisTest.getWCPathSet(),
3827                             "Move files", Depth.infinity, false, false,
3828                             null, null);
3829         thisTest.checkStatus();
3830 
3831         // modify A/B/E/alpha in second working copy
3832         File alpha = new File(tcTest.getWorkingCopy(), "A/B/E/alpha");
3833         PrintWriter alphaWriter = new PrintWriter(new FileOutputStream(alpha, true));
3834         alphaWriter.print("appended alpha text");
3835         alphaWriter.close();
3836 
3837         // update the tc test
3838         assertEquals("wrong revision number from update",
3839                      update(tcTest), 2);
3840 
3841         // set the expected working copy layout for the tc test
3842         tcTest.getWc().addItem("A/B/F/alpha",
3843                 tcTest.getWc().getItemContent("A/B/E/alpha"));
3844         tcTest.getWc().setItemWorkingCopyRevision("A/B/F/alpha", 2);
3845         // we expect the tree conflict to turn the existing item into
3846         // a scheduled-add with history.
3847         tcTest.getWc().setItemTextStatus("A/B/E/alpha", Status.Kind.added);
3848         tcTest.getWc().setItemTextStatus("A/B/F/alpha", Status.Kind.normal);
3849 
3850         // check the status of the working copy of the tc test
3851         tcTest.checkStatus();
3852 
3853         // get the Info2 of the tree conflict
3854         MyInfoCallback callback = new MyInfoCallback();
3855         client.info2(tcTest.getWCPath() + "/A/B/E/alpha", null,
3856                 null, Depth.unknown, null, callback);
3857         Set<ConflictDescriptor> conflicts = callback.getInfo().getConflicts();
3858         assertNotNull("Conflict should not be null", conflicts);
3859         ConflictDescriptor conflict = conflicts.iterator().next();
3860 
3861         assertNotNull("Conflict should not be null", conflict);
3862         assertNotNull("Repository UUID must be set", conflict.getSrcLeftVersion().getReposUUID());
3863 
3864         assertEquals(conflict.getSrcLeftVersion().getNodeKind(), NodeKind.file);
3865         assertEquals(conflict.getSrcLeftVersion().getReposURL() + "/" +
3866                 conflict.getSrcLeftVersion().getPathInRepos(), tcTest.getUrl() + "/A/B/E/alpha");
3867         assertEquals(conflict.getSrcLeftVersion().getPegRevision(), 1L);
3868 
3869         if (conflict.getSrcRightVersion() != null)
3870         {
3871             assertEquals(conflict.getSrcLeftVersion().getReposUUID(),
3872                          conflict.getSrcRightVersion().getReposUUID());
3873             assertEquals(conflict.getSrcRightVersion().getNodeKind(), NodeKind.none);
3874             assertEquals(conflict.getSrcRightVersion().getReposURL(), tcTest.getUrl().toString());
3875             assertEquals(conflict.getSrcRightVersion().getPegRevision(), 2L);
3876         }
3877     }
3878 
3879     /**
3880      * Test the basic SVNClient.propertySetRemote functionality.
3881      * @throws Throwable
3882      */
testPropEdit()3883     public void testPropEdit() throws Throwable
3884     {
3885         final String PROP = "abc";
3886         final byte[] VALUE = new String("def").getBytes();
3887         final byte[] NEWVALUE = new String("newvalue").getBytes();
3888         // create the test working copy
3889         OneTest thisTest = new OneTest();
3890 
3891         Set<String> pathSet = new HashSet<String>();
3892         // set a property on A/D/G/rho file
3893         pathSet.clear();
3894         pathSet.add(thisTest.getWCPath()+"/A/D/G/rho");
3895         client.propertySetLocal(pathSet, PROP, VALUE,
3896                                 Depth.infinity, null, false);
3897         thisTest.getWc().setItemPropStatus("A/D/G/rho", Status.Kind.modified);
3898 
3899         // test the status of the working copy
3900         thisTest.checkStatus();
3901 
3902         // commit the changes
3903         checkCommitRevision(thisTest, "wrong revision number from commit", 2,
3904                             thisTest.getWCPathSet(), "log msg", Depth.infinity,
3905                             false, false, null, null);
3906 
3907         thisTest.getWc().setItemPropStatus("A/D/G/rho", Status.Kind.normal);
3908 
3909         // check the status of the working copy
3910         thisTest.checkStatus();
3911 
3912         // now edit the propval directly in the repository
3913         long baseRev = 2L;
3914         client.propertySetRemote(thisTest.getUrl()+"/A/D/G/rho", baseRev, PROP, NEWVALUE,
3915                                  new ConstMsg("edit prop"), false, null, null);
3916 
3917         // update the WC and verify that the property was changed
3918         client.update(thisTest.getWCPathSet(), Revision.HEAD, Depth.infinity, false, false,
3919                       false, false);
3920         byte[] propVal = client.propertyGet(thisTest.getWCPath()+"/A/D/G/rho", PROP, null, null);
3921 
3922         assertEquals(new String(propVal), new String(NEWVALUE));
3923 
3924     }
3925 
3926     /**
3927      * Test tolerance of unversioned obstructions when adding paths with
3928      * {@link org.apache.subversion.javahl.SVNClient#checkout()},
3929      * {@link org.apache.subversion.javahl.SVNClient#update()}, and
3930      * {@link org.apache.subversion.javahl.SVNClient#doSwitch()}
3931      * @throws IOException
3932      * @throws SubversionException
3933      */
3934     /*
3935       This is currently commented out, because we don't have an XFail method
3936       for JavaHL.  The resolution is pending the result of issue #3680:
3937       https://issues.apache.org/jira/browse/SVN-3680
3938 
3939     public void testObstructionTolerance()
3940             throws SubversionException, IOException
3941     {
3942         // build the test setup
3943         OneTest thisTest = new OneTest();
3944 
3945         File file;
3946         PrintWriter pw;
3947 
3948         // ----- TEST CHECKOUT -----
3949         // Use export to make unversioned obstructions for a second
3950         // WC checkout (deleting export target from previous tests
3951         // first if it exists).
3952         String secondWC = thisTest.getWCPath() + ".backup1";
3953         removeDirOrFile(new File(secondWC));
3954         client.doExport(thisTest.getUrl(), secondWC, null, null, false, false,
3955                         Depth.infinity, null);
3956 
3957         // Make an obstructing file that conflicts with add coming from repos
3958         file = new File(secondWC, "A/B/lambda");
3959         pw = new PrintWriter(new FileOutputStream(file));
3960         pw.print("This is the conflicting obstructiong file 'lambda'.");
3961         pw.close();
3962 
3963         // Attempt to checkout backup WC without "--force"...
3964         try
3965         {
3966             // ...should fail
3967             client.checkout(thisTest.getUrl(), secondWC, null, null,
3968                             Depth.infinity, false, false);
3969             fail("obstructed checkout should fail by default");
3970         }
3971         catch (ClientException expected)
3972         {
3973         }
3974 
3975         // Attempt to checkout backup WC with "--force"
3976         // so obstructions are tolerated
3977         client.checkout(thisTest.getUrl(), secondWC, null, null,
3978                         Depth.infinity, false, true);
3979 
3980         // Check the WC status, the only status should be a text
3981         // mod to lambda.  All the other obstructing files were identical
3982         MyStatusCallback statusCallback = new MyStatusCallback();
3983         client.status(secondWC, Depth.unknown, false, false, false, false,
3984                     null, statusCallback);
3985         Status[] secondWCStatus = statusCallback.getStatusArray();
3986         if (!(secondWCStatus.length == 1 &&
3987             secondWCStatus[0].getPath().endsWith("A/B/lambda") &&
3988             secondWCStatus[0].getTextStatus() == Status.Kind.modified &&
3989             secondWCStatus[0].getPropStatus() == Status.Kind.none))
3990         {
3991             fail("Unexpected WC status after co with " +
3992                  "unversioned obstructions");
3993         }
3994 
3995         // Make a third WC to test obstruction tolerance of sw and up.
3996         OneTest backupTest = thisTest.copy(".backup2");
3997 
3998         // ----- TEST UPDATE -----
3999         // r2: Add a file A/D/H/nu
4000         file = new File(thisTest.getWorkingCopy(), "A/D/H/nu");
4001         pw = new PrintWriter(new FileOutputStream(file));
4002         pw.print("This is the file 'nu'.");
4003         pw.close();
4004         client.add(file.getAbsolutePath(), Depth.empty, false, false, false);
4005         addExpectedCommitItem(thisTest.getWCPath(), thisTest.getUrl(),
4006                               "A/D/H/nu", NodeKind.file,
4007                               CommitItemStateFlags.TextMods +
4008                               CommitItemStateFlags.Add);
4009         assertEquals("wrong revision number from commit",
4010                      commit(thisTest, "log msg"), 2);
4011         thisTest.getWc().addItem("A/D/H/nu", "This is the file 'nu'.");
4012         statusCallback = new MyStatusCallback();
4013         client.status(thisTest.getWCPath() + "/A/D/H/nu", Depth.immediates,
4014                       false, true, false, false, null, statusCallback);
4015         Status status = statusCallback.getStatusArray()[0];
4016 
4017         // Add an unversioned file A/D/H/nu to the backup WC
4018         file = new File(backupTest.getWorkingCopy(), "A/D/H/nu");
4019         pw = new PrintWriter(new FileOutputStream(file));
4020         pw.print("This is the file 'nu'.");
4021         pw.close();
4022 
4023         // Attempt to update backup WC without "--force"
4024         try
4025         {
4026             // obstructed update should fail
4027             update(backupTest);
4028             fail("obstructed update should fail by default");
4029         }
4030         catch (ClientException expected)
4031         {
4032         }
4033 
4034         // Attempt to update backup WC with "--force"
4035         assertEquals("wrong revision from update",
4036                      client.update(backupTest.getWCPathSet(),
4037                                    null, Depth.infinity, false, false,
4038                                    true)[0],
4039                      2);
4040 
4041         // ----- TEST SWITCH -----
4042         // Add an unversioned file A/B/E/nu to the backup WC
4043         // The file differs from A/D/H/nu
4044         file = new File(backupTest.getWorkingCopy(), "A/B/E/nu");
4045         pw = new PrintWriter(new FileOutputStream(file));
4046         pw.print("This is yet another file 'nu'.");
4047         pw.close();
4048 
4049         // Add an unversioned file A/B/E/chi to the backup WC
4050         // The file is identical to A/D/H/chi.
4051         file = new File(backupTest.getWorkingCopy(), "A/B/E/chi");
4052         pw = new PrintWriter(new FileOutputStream(file));
4053         pw.print("This is the file 'chi'.");
4054         pw.close();
4055 
4056         // Attempt to switch A/B/E to A/D/H without "--force"
4057         try
4058         {
4059             // obstructed switch should fail
4060             client.doSwitch(backupTest.getWCPath() + "/A/B/E",
4061                             backupTest.getUrl() + "/A/D/H",
4062                             null, Revision.HEAD, Depth.files, false, false,
4063                             false);
4064             fail("obstructed switch should fail by default");
4065         }
4066         catch (ClientException expected)
4067         {
4068         }
4069 
4070         // Complete the switch using "--force" and check the status
4071         client.doSwitch(backupTest.getWCPath() + "/A/B/E",
4072                         backupTest.getUrl() + "/A/D/H",
4073                         Revision.HEAD, Revision.HEAD, Depth.infinity,
4074                         false, false, true);
4075 
4076         backupTest.getWc().setItemIsSwitched("A/B/E",true);
4077         backupTest.getWc().removeItem("A/B/E/alpha");
4078         backupTest.getWc().removeItem("A/B/E/beta");
4079         backupTest.getWc().addItem("A/B/E/nu",
4080                                    "This is yet another file 'nu'.");
4081         backupTest.getWc().setItemTextStatus("A/B/E/nu", Status.Kind.modified);
4082         backupTest.getWc().addItem("A/D/H/nu",
4083                                    "This is the file 'nu'.");
4084         backupTest.getWc().addItem("A/B/E/chi",
4085                                    backupTest.getWc().getItemContent("A/D/H/chi"));
4086         backupTest.getWc().addItem("A/B/E/psi",
4087                                    backupTest.getWc().getItemContent("A/D/H/psi"));
4088         backupTest.getWc().addItem("A/B/E/omega",
4089                                    backupTest.getWc().getItemContent("A/D/H/omega"));
4090 
4091         backupTest.checkStatus();
4092     }*/
4093 
4094     /**
4095      * Test basic blame functionality.  This test marginally tests blame
4096      * correctness, mainly just that the blame APIs link correctly.
4097      * @throws Throwable
4098      * @since 1.5
4099      */
4100     @SuppressWarnings("deprecation")
testBasicBlame()4101     public void testBasicBlame() throws Throwable
4102     {
4103         OneTest thisTest = new OneTest();
4104         // Test the old interface to be sure it still works
4105         byte[] result = collectBlameLines(thisTest.getWCPath() + "/iota",
4106                                           Revision.getInstance(1),
4107                                           Revision.getInstance(1),
4108                                           Revision.getInstance(1),
4109                                           false, false);
4110         assertEquals("     1    jrandom This is the file 'iota'.\n",
4111                      new String(result));
4112 
4113         // Test the current interface
4114         BlameCallbackImpl callback = new BlameCallbackImpl();
4115         client.blame(thisTest.getWCPath() + "/iota", Revision.getInstance(1),
4116                      Revision.getInstance(1), Revision.getInstance(1),
4117                      false, false, callback);
4118         assertEquals(1, callback.numberOfLines());
4119         BlameCallbackImpl.BlameLine line = callback.getBlameLine(0);
4120         assertNotNull(line);
4121         assertEquals(1, line.getRevision());
4122         assertEquals("jrandom", line.getAuthor());
4123         assertEquals("This is the file 'iota'.", line.getLine());
4124     }
4125 
4126     /**
4127      * Test blame with diff options.
4128      * @since 1.9
4129      */
4130     @SuppressWarnings("deprecation")
testBlameWithDiffOptions()4131     public void testBlameWithDiffOptions() throws Throwable
4132     {
4133         OneTest thisTest = new OneTest();
4134         // Modify the file iota, making only whitespace changes.
4135         File iota = new File(thisTest.getWorkingCopy(), "iota");
4136         FileOutputStream stream = new FileOutputStream(iota, false);
4137         stream.write("This   is   the   file   'iota'.\t".getBytes());
4138         stream.close();
4139         Set<String> srcPaths = new HashSet<String>(1);
4140         srcPaths.add(thisTest.getWCPath());
4141         try {
4142             client.username("rayjandom");
4143             client.commit(srcPaths, Depth.infinity, false, false, null, null,
4144                           new ConstMsg("Whitespace-only change in /iota"), null);
4145         } finally {
4146             client.username("jrandom");
4147         }
4148 
4149         // Run blame on the result
4150         BlameCallbackImpl callback = new BlameCallbackImpl();
4151         client.blame(thisTest.getWCPath() + "/iota", Revision.HEAD,
4152                      Revision.getInstance(1), Revision.HEAD,
4153                      false, false, callback,
4154                      new DiffOptions(DiffOptions.Flag.IgnoreWhitespace));
4155         assertEquals(1, callback.numberOfLines());
4156         BlameCallbackImpl.BlameLine line = callback.getBlameLine(0);
4157         assertNotNull(line);
4158         assertEquals(1, line.getRevision());
4159         assertEquals("jrandom", line.getAuthor());
4160         assertEquals("This   is   the   file   'iota'.\t", line.getLine());
4161     }
4162 
4163     /**
4164      * Test the new 1.12 blame interface on a file with null bytes.
4165      * @throws Throwable
4166      * @since 1.12
4167      */
testBinaryBlame()4168     public void testBinaryBlame() throws Throwable
4169     {
4170         final byte[] lineIn = {0x0, 0x0, 0x0, 0xa};
4171         final byte[] lineOut = {0x0, 0x0, 0x0};
4172 
4173         OneTest thisTest = new OneTest();
4174         // Modify the file iota, adding null bytes.
4175         File iota = new File(thisTest.getWorkingCopy(), "iota");
4176         FileOutputStream stream = new FileOutputStream(iota, false);
4177         stream.write(lineIn);
4178         stream.close();
4179         Set<String> srcPaths = new HashSet<String>(1);
4180         srcPaths.add(thisTest.getWCPath());
4181         try {
4182             client.username("rayjandom");
4183             client.commit(srcPaths, Depth.infinity, false, false, null, null,
4184                           new ConstMsg("NUL bytes written to /iota"), null);
4185         } finally {
4186             client.username("jrandom");
4187         }
4188 
4189         // Test the current interface
4190         BlameRangeCallbackImpl rangeCallback = new BlameRangeCallbackImpl();
4191         BlameLineCallbackImpl lineCallback = new BlameLineCallbackImpl();
4192         client.blame(thisTest.getWCPath() + "/iota", Revision.HEAD,
4193                      Revision.getInstance(0), Revision.HEAD,
4194                      false, false, null, rangeCallback, lineCallback);
4195         assertEquals(0, rangeCallback.startRevnum);
4196         assertEquals(2, rangeCallback.endRevnum);
4197         assertEquals(1, lineCallback.numberOfLines());
4198 
4199         BlameLineCallbackImpl.BlameLine line = lineCallback.getBlameLine(0);
4200         assertNotNull(line);
4201         assertEquals(2, line.getRevision());
4202         assertEquals("rayjandom", line.getAuthor());
4203         assertArrayEquals(lineOut, line.getLine());
4204     }
4205 
4206     /**
4207      * Test commit of arbitrary revprops.
4208      * @throws Throwable
4209      * @since 1.5
4210      */
testCommitRevprops()4211     public void testCommitRevprops() throws Throwable
4212     {
4213         // build the test setup
4214         OneTest thisTest = new OneTest();
4215 
4216         // modify file A/mu
4217         File mu = new File(thisTest.getWorkingCopy(), "A/mu");
4218         PrintWriter muWriter = new PrintWriter(new FileOutputStream(mu, true));
4219         muWriter.print("appended mu text");
4220         muWriter.close();
4221         thisTest.getWc().setItemWorkingCopyRevision("A/mu", 2);
4222         thisTest.getWc().setItemContent("A/mu",
4223                 thisTest.getWc().getItemContent("A/mu") + "appended mu text");
4224         addExpectedCommitItem(thisTest.getWCPath(),
4225                 thisTest.getUrl().toString(), "A/mu",NodeKind.file,
4226                 CommitItemStateFlags.TextMods);
4227 
4228         // commit the changes, with some extra revprops
4229         Map<String, String> revprops = new HashMap<String, String>();
4230         revprops.put("kfogel", "rockstar");
4231         revprops.put("cmpilato", "theman");
4232         checkCommitRevision(thisTest, "wrong revision number from commit", 2,
4233                             thisTest.getWCPathSet(), "log msg",
4234                             Depth.infinity, true, true, null, revprops);
4235 
4236         // check the status of the working copy
4237         thisTest.checkStatus();
4238 
4239         // Fetch our revprops from the server
4240         final List<Map<String, byte[]>> revpropList =
4241                             new ArrayList<Map<String, byte[]>>();
4242         Set<String> revProps = new HashSet<String>(2);
4243         revProps.add("kfogel");
4244         revProps.add("cmpilato");
4245         // Testing variant with allRevProps = false
4246         client.logMessages(thisTest.getWCPath(), Revision.getInstance(2),
4247                 toRevisionRange(Revision.getInstance(2),
4248                                 Revision.getInstance(2)),
4249                 false, false, false, revProps, false, 0,
4250                 new LogMessageCallback () {
4251                     public void singleMessage(Set<ChangePath> changedPaths,
4252                                               long revision,
4253                                               Map<String, byte[]> revprops,
4254                                               boolean hasChildren)
4255                   { revpropList.add(revprops); }
4256                 });
4257         Map<String, byte[]> fetchedProps = revpropList.get(0);
4258 
4259         assertEquals("wrong number of fetched revprops", revprops.size(),
4260                      fetchedProps.size());
4261         Set<String> keys = fetchedProps.keySet();
4262         for (String key : keys)
4263           {
4264             assertEquals("revprops check", revprops.get(key),
4265                          new String(fetchedProps.get(key)));
4266           }
4267     }
4268 
4269     /**
4270      * Test an explicit expose of SVNClient.
4271      * (This used to cause a fatal exception in the Java Runtime)
4272      */
testDispose()4273     public void testDispose() throws Throwable
4274     {
4275       SVNClient cl = new SVNClient();
4276       cl.dispose();
4277     }
4278 
4279     /**
4280      * Test RevisionRangeList.remove
4281      */
testRevisionRangeListRemove()4282     public void testRevisionRangeListRemove() throws Throwable
4283     {
4284         RevisionRangeList ranges =
4285             new RevisionRangeList(new ArrayList<RevisionRange>());
4286         ranges.getRanges()
4287             .add(new RevisionRange(Revision.getInstance(1),
4288                                    Revision.getInstance(5),
4289                                    true));
4290         ranges.getRanges()
4291             .add(new RevisionRange(Revision.getInstance(7),
4292                                    Revision.getInstance(9),
4293                                    false));
4294         RevisionRangeList eraser =
4295             new RevisionRangeList(new ArrayList<RevisionRange>());
4296         eraser.getRanges()
4297             .add(new RevisionRange(Revision.getInstance(7),
4298                                    Revision.getInstance(9),
4299                                    true));
4300 
4301         List<RevisionRange> result = ranges.remove(eraser, true).getRanges();
4302         assertEquals(2, ranges.getRanges().size());
4303         assertEquals(1, eraser.getRanges().size());
4304         assertEquals(2, result.size());
4305 
4306         result = ranges.remove(eraser.getRanges(), false);
4307         assertEquals(2, ranges.getRanges().size());
4308         assertEquals(1, eraser.getRanges().size());
4309         assertEquals(1, result.size());
4310     }
4311 
4312     private class Tunnel extends Thread
4313         implements TunnelAgent, TunnelAgent.CloseTunnelCallback
4314     {
checkTunnel(String name)4315         public boolean checkTunnel(String name)
4316         {
4317             return name.equals("test");
4318         }
4319 
4320         public TunnelAgent.CloseTunnelCallback
openTunnel(ReadableByteChannel request, WritableByteChannel response, String name, String user, String hostname, int port)4321             openTunnel(ReadableByteChannel request,
4322                        WritableByteChannel response,
4323                        String name, String user,
4324                        String hostname, int port)
4325         {
4326             this.request = request;
4327             this.response = response;
4328             start();
4329             return this;
4330         }
4331 
closeTunnel()4332         public void closeTunnel()
4333         {
4334             Throwable t = null;
4335             try {
4336                 request.close();
4337                 join();
4338                 response.close();
4339             } catch (Throwable ex) {
4340                 t = ex;
4341             }
4342             assertEquals("No exception thrown", null, t);
4343         }
4344 
4345         private ReadableByteChannel request;
4346         private WritableByteChannel response;
4347 
run()4348         public void run()
4349         {
4350 
4351             int index = 0;
4352             byte[] raw_data = new byte[1024];
4353             ByteBuffer data = ByteBuffer.wrap(raw_data);
4354             while(index < commands.length && request.isOpen()) {
4355                 try {
4356                     byte[] command = commands[index++];
4357                     response.write(ByteBuffer.wrap(command));
4358                 } catch (IOException ex) {
4359                     break;
4360                 }
4361 
4362                 try {
4363                     data.clear();
4364                     request.read(data);
4365                 } catch (Throwable ex) {}
4366             }
4367 
4368             try {
4369                 response.close();
4370                 request.close();
4371             } catch (Throwable t) {}
4372         }
4373 
4374         private final byte[][] commands = new byte[][]{
4375             // Initial capabilities negotiation
4376             ("( success ( 2 2 ( ) " +
4377              "( edit-pipeline svndiff1 absent-entries commit-revprops " +
4378              "depth log-revprops atomic-revprops partial-replay " +
4379              "inherited-props ephemeral-txnprops file-revs-reverse " +
4380              ") ) ) ").getBytes(),
4381 
4382             // Response for successful connection
4383             ("( success ( ( ANONYMOUS EXTERNAL ) " +
4384              "36:e3c8c113-03ba-4ec5-a8e6-8fc555e57b91 ) ) ").getBytes(),
4385 
4386             // Response to authentication request
4387             ("( success ( ) ) ( success ( " +
4388              "36:e3c8c113-03ba-4ec5-a8e6-8fc555e57b91 " +
4389              "24:svn+test://localhost/foo ( mergeinfo ) ) ) ").getBytes(),
4390 
4391             // Response to revprop request
4392             ("( success ( ( ) 0: ) ) ( success ( ( 4:fake ) ) ) ").getBytes()
4393         };
4394     }
4395 
4396     /**
4397      * Test tunnel handling.
4398      */
testTunnelAgent()4399     public void testTunnelAgent() throws Throwable
4400     {
4401         byte[] revprop;
4402         SVNClient cl = new SVNClient();
4403         try {
4404             cl.notification2(new MyNotifier());
4405             if (DefaultAuthn.useDeprecated())
4406                 cl.setPrompt(DefaultAuthn.getDeprecated());
4407             else
4408                 cl.setPrompt(DefaultAuthn.getDefault());
4409             cl.username(USERNAME);
4410             cl.setProgressCallback(new DefaultProgressListener());
4411             cl.setConfigDirectory(conf.getAbsolutePath());
4412 
4413             cl.setTunnelAgent(new Tunnel());
4414             revprop = cl.revProperty("svn+test://localhost/foo", "svn:log",
4415                                      Revision.getInstance(0L));
4416         } finally {
4417             cl.dispose();
4418         }
4419         assertEquals("fake", new String(revprop));
4420     }
4421 
4422     public static int FLAG_ECHO          = 0x00000001;
4423     public static int FLAG_THROW_IN_OPEN = 0x00000002;
4424 
4425     public enum Actions
4426     {
4427         READ_CLIENT,    // Read a request from SVN client
4428         EMUL_SERVER,    // Emulate server response
4429         WAIT_TUNNEL,    // Wait for tunnel to be closed
4430     };
4431 
4432     public static class ScriptItem
4433     {
4434         Actions action;
4435         String value;
4436 
ScriptItem(Actions action, String value)4437         ScriptItem(Actions action, String value)
4438         {
4439             this.action = action;
4440             this.value = value;
4441         }
4442     }
4443 
4444     private static class TestTunnelAgent extends Thread
4445         implements TunnelAgent
4446     {
4447         ScriptItem[] script;
4448         int flags;
4449         String error = null;
4450         ReadableByteChannel request;
4451         WritableByteChannel response;
4452 
4453         final CloseTunnelCallback closeTunnelCallback = () ->
4454         {
4455             if ((flags & FLAG_ECHO) != 0)
4456                 System.out.println("TunnelAgent.CloseTunnelCallback");
4457         };
4458 
TestTunnelAgent(int flags, ScriptItem[] script)4459         TestTunnelAgent(int flags, ScriptItem[] script)
4460         {
4461             this.flags = flags;
4462             this.script = script;
4463         }
4464 
joinAndTest()4465         public void joinAndTest()
4466         {
4467             try
4468             {
4469                 join();
4470             }
4471             catch (InterruptedException e)
4472             {
4473                 fail("InterruptedException was caught");
4474             }
4475 
4476             if (error != null)
4477                 fail(error);
4478         }
4479 
4480         @Override
checkTunnel(String name)4481         public boolean checkTunnel(String name)
4482         {
4483             return true;
4484         }
4485 
readClient(ByteBuffer readBuffer)4486         private String readClient(ByteBuffer readBuffer)
4487             throws IOException
4488         {
4489             readBuffer.reset();
4490             request.read(readBuffer);
4491 
4492             final int offset = readBuffer.arrayOffset();
4493             return new String(readBuffer.array(),
4494                 offset,
4495                 readBuffer.position() - offset);
4496         }
4497 
emulateServer(String serverMessage)4498         private void emulateServer(String serverMessage)
4499             throws IOException
4500         {
4501             final byte[] responseBytes = serverMessage.getBytes();
4502             response.write(ByteBuffer.wrap(responseBytes));
4503         }
4504 
doScriptItem(ScriptItem scriptItem, ByteBuffer readBuffer)4505         private void doScriptItem(ScriptItem scriptItem, ByteBuffer readBuffer)
4506             throws Exception
4507         {
4508             switch (scriptItem.action)
4509             {
4510             case READ_CLIENT:
4511                 final String actualLine = readClient(readBuffer);
4512 
4513                 if ((flags & FLAG_ECHO) != 0)
4514                 {
4515                     System.out.println("SERVER: " + scriptItem.value);
4516                     System.out.flush();
4517                 }
4518 
4519                 if (!actualLine.contains(scriptItem.value))
4520                 {
4521                     System.err.println("Expected: " + scriptItem.value);
4522                     System.err.println("Actual:   " + actualLine);
4523                     System.err.flush();
4524 
4525                     // Unblock the SVN thread by emulating a server error
4526                     final String serverError = "( success ( ( ) 0: ) ) ( failure ( ( 160000 39:Test script received unexpected request 0: 0 ) ) ) ";
4527                     emulateServer(serverError);
4528 
4529                     fail("Unexpected client request");
4530                 }
4531                 break;
4532             case EMUL_SERVER:
4533                 if ((flags & FLAG_ECHO) != 0)
4534                 {
4535                     System.out.println("CLIENT: " + scriptItem.value);
4536                     System.out.flush();
4537                 }
4538 
4539                 emulateServer(scriptItem.value);
4540                 break;
4541             case WAIT_TUNNEL:
4542                 // The loop will end with an exception when tunnel is closed
4543                 for (;;)
4544                 {
4545                     readClient(readBuffer);
4546                 }
4547             }
4548         }
4549 
run()4550         public void run()
4551         {
4552             final ByteBuffer readBuffer = ByteBuffer.allocate(1024 * 1024);
4553             readBuffer.mark();
4554 
4555             for (ScriptItem scriptItem : script)
4556             {
4557                 try
4558                 {
4559                     doScriptItem(scriptItem, readBuffer);
4560                 }
4561                 catch (ClosedChannelException ex)
4562                 {
4563                     // Expected when closed properly
4564                 }
4565                 catch (IOException e)
4566                 {
4567                     // IOException occurs when already-freed apr_file_t was lucky
4568                     // to have reasonable fields to avoid the crash. It still
4569                     // indicates a problem.
4570                     error = "IOException was caught in run()";
4571                     return;
4572                 }
4573                 catch (Throwable t)
4574                 {
4575                     // No other exceptions are expected here.
4576                     error = "Exception was caught in run()";
4577                     t.printStackTrace();
4578                     return;
4579                 }
4580             }
4581         }
4582 
4583         @Override
openTunnel(ReadableByteChannel request, WritableByteChannel response, String name, String user, String hostname, int port)4584         public CloseTunnelCallback openTunnel(ReadableByteChannel request,
4585                                               WritableByteChannel response,
4586                                               String name,
4587                                               String user,
4588                                               String hostname,
4589                                               int port)
4590             throws Throwable
4591         {
4592             this.request = request;
4593             this.response = response;
4594 
4595             start();
4596 
4597             if ((flags & FLAG_THROW_IN_OPEN) != 0)
4598                 throw ClientException.fromException(new RuntimeException("Test exception"));
4599 
4600             return closeTunnelCallback;
4601         }
4602     };
4603 
4604     /**
4605      * Test scenario which previously caused a JVM crash.
4606      * In this scenario, GC is invoked before closing tunnel.
4607      */
testCrash_RemoteSession_nativeDispose()4608     public void testCrash_RemoteSession_nativeDispose()
4609     {
4610         final ScriptItem[] script = new ScriptItem[]
4611         {
4612             new ScriptItem(Actions.EMUL_SERVER, "( success ( 2 2 ( ) ( edit-pipeline svndiff1 absent-entries commit-revprops depth log-revprops atomic-revprops partial-replay inherited-props ephemeral-txnprops file-revs-reverse ) ) ) "),
4613             new ScriptItem(Actions.READ_CLIENT, "edit-pipeline"),
4614             new ScriptItem(Actions.EMUL_SERVER, "( success ( ( ANONYMOUS ) 36:0113e071-0208-4a7b-9f20-3038f9caf0f0 ) ) "),
4615             new ScriptItem(Actions.READ_CLIENT, "ANONYMOUS"),
4616             new ScriptItem(Actions.EMUL_SERVER, "( success ( ) ) ( success ( 36:00000000-0000-0000-0000-000000000000 25:svn+test://localhost/test ( mergeinfo ) ) ) "),
4617         };
4618 
4619         final TestTunnelAgent tunnelAgent = new TestTunnelAgent(0, script);
4620         final RemoteFactory remoteFactory = new RemoteFactory();
4621         remoteFactory.setTunnelAgent(tunnelAgent);
4622 
4623         ISVNRemote remote = null;
4624         try
4625         {
4626             remote = remoteFactory.openRemoteSession("svn+test://localhost/test", 1);
4627         }
4628         catch (SubversionException e)
4629         {
4630             fail("SubversionException was caught");
4631         }
4632 
4633         // Previously, 'OperationContext::openTunnel()' didn't 'NewGlobalRef()'
4634         // callback returned by 'TunnelAgent.openTunnel()'. This caused JVM to
4635         // dispose it on next GC. JavaHL calls callback in 'remote.dispose()'.
4636         // If the callback was disposed, this caused a JVM crash.
4637         System.gc();
4638         remote.dispose();
4639 
4640         tunnelAgent.joinAndTest();
4641     }
4642 
4643     /**
4644      * Test scenario which previously caused a JVM crash.
4645      * In this scenario, tunnel was not properly closed after exception in
4646      * 'TunnelAgent.openTunnel()'.
4647      */
testCrash_RequestChannel_nativeRead_AfterException()4648     public void testCrash_RequestChannel_nativeRead_AfterException()
4649     {
4650         // Previously, exception caused TunnelChannel's native side to be
4651         // destroyed with the following abbreviated stack:
4652         //   TunnelChannel.nativeClose()
4653         //   svn_pool_destroy(sesspool)
4654         //   svn_ra_open5()
4655         // TunnelAgent was unaware and called 'RequestChannel.nativeRead()'
4656         // or 'ResponseChannel.nativeWrite()', causing either a crash or
4657         // an attempt to use a random file.
4658         final int flags = FLAG_THROW_IN_OPEN;
4659 
4660         final ScriptItem[] script = new ScriptItem[]
4661         {
4662             new ScriptItem(Actions.EMUL_SERVER, "( success ( 2 2 ( ) ( edit-pipeline svndiff1 absent-entries commit-revprops depth log-revprops atomic-revprops partial-replay inherited-props ephemeral-txnprops file-revs-reverse ) ) ) "),
4663             new ScriptItem(Actions.WAIT_TUNNEL, ""),
4664         };
4665 
4666         final TestTunnelAgent tunnelAgent = new TestTunnelAgent(flags, script);
4667         final SVNClient svnClient = new SVNClient();
4668         svnClient.setTunnelAgent(tunnelAgent);
4669 
4670         try
4671         {
4672             svnClient.openRemoteSession("svn+test://localhost/test");
4673         }
4674         catch (SubversionException e)
4675         {
4676             // RuntimeException("Test exception") is expected here
4677         }
4678 
4679         tunnelAgent.joinAndTest();
4680     }
4681 
4682     /**
4683      * Test scenario which previously caused a JVM crash.
4684      * In this scenario, tunnel was not properly closed after an SVN error.
4685      */
testCrash_RequestChannel_nativeRead_AfterSvnError()4686     public void testCrash_RequestChannel_nativeRead_AfterSvnError()
4687     {
4688         final String wcRoot = new File("tempSvnRepo").getAbsolutePath();
4689 
4690         final ScriptItem[] script = new ScriptItem[]
4691         {
4692             // openRemoteSession
4693             new ScriptItem(Actions.EMUL_SERVER, "( success ( 2 2 ( ) ( edit-pipeline svndiff1 absent-entries commit-revprops depth log-revprops atomic-revprops partial-replay inherited-props ephemeral-txnprops file-revs-reverse ) ) ) "),
4694             new ScriptItem(Actions.READ_CLIENT, "edit-pipeline"),
4695             new ScriptItem(Actions.EMUL_SERVER, "( success ( ( ANONYMOUS ) 36:0113e071-0208-4a7b-9f20-3038f9caf0f0 ) ) "),
4696             new ScriptItem(Actions.READ_CLIENT, "ANONYMOUS"),
4697             new ScriptItem(Actions.EMUL_SERVER, "( success ( ) ) ( success ( 36:00000000-0000-0000-0000-000000000000 25:svn+test://localhost/test ( mergeinfo ) ) ) "),
4698             // checkout
4699             new ScriptItem(Actions.READ_CLIENT, "( get-latest-rev ( ) ) "),
4700             // Previously, error caused a SubversionException to be created,
4701             // which then skipped closing the Tunnel properly due to
4702             // 'ExceptionOccurred()' in 'OperationContext::closeTunnel()'.
4703             // If TunnelAgent was unaware and called 'RequestChannel.nativeRead()',
4704             // it either crashed or tried to use a random file.
4705             new ScriptItem(Actions.EMUL_SERVER, "( success ( ( ) 0: ) ) ( failure ( ( 160006 20:This is a test error 0: 0 ) ) ) "),
4706             // Pretend that TunnelAgent tries to read more
4707             new ScriptItem(Actions.WAIT_TUNNEL, ""),
4708         };
4709 
4710         final TestTunnelAgent tunnelAgent = new TestTunnelAgent(0, script);
4711         final SVNClient svnClient = new SVNClient();
4712         svnClient.setTunnelAgent(tunnelAgent);
4713 
4714         try
4715         {
4716             svnClient.checkout("svn+test://localhost/test",
4717                                wcRoot,
4718                                Revision.getInstance(1),
4719                                null,
4720                                Depth.infinity,
4721                                true,
4722                                false);
4723 
4724             svnClient.dispose();
4725         }
4726         catch (ClientException ex)
4727         {
4728             final int SVN_ERR_FS_NO_SUCH_REVISION = 160006;
4729             if (SVN_ERR_FS_NO_SUCH_REVISION != ex.getAllMessages().get(0).getCode())
4730                 ex.printStackTrace();
4731         }
4732 
4733         tunnelAgent.joinAndTest();
4734     }
4735 
4736     /**
4737      * @return <code>file</code> converted into a -- possibly
4738      * <code>canonical</code>-ized -- Subversion-internal path
4739      * representation.
4740      */
fileToSVNPath(File file, boolean canonical)4741     private String fileToSVNPath(File file, boolean canonical)
4742     {
4743         // JavaHL need paths with '/' separators
4744         if (canonical)
4745         {
4746             try
4747             {
4748                 return file.getCanonicalPath().replace('\\', '/');
4749             }
4750             catch (IOException e)
4751             {
4752                 return null;
4753             }
4754         }
4755         else
4756         {
4757             return file.getPath().replace('\\', '/');
4758         }
4759     }
4760 
toRevisionRange(Revision rev1, Revision rev2)4761     private List<RevisionRange> toRevisionRange(Revision rev1, Revision rev2)
4762     {
4763         List<RevisionRange> ranges = new ArrayList<RevisionRange>(1);
4764         ranges.add(new RevisionRange(rev1, rev2));
4765         return ranges;
4766     }
4767 
4768     /**
4769      * A DiffSummaryReceiver implementation which collects all DiffSummary
4770      * notifications.
4771      */
4772     private static class DiffSummaries extends HashMap<String, DiffSummary>
4773         implements DiffSummaryCallback
4774     {
4775         // Update the serialVersionUID when there is a incompatible
4776         // change made to this class.
4777         private static final long serialVersionUID = 1L;
4778 
onSummary(DiffSummary descriptor)4779         public void onSummary(DiffSummary descriptor)
4780         {
4781             super.put(descriptor.getPath(), descriptor);
4782         }
4783     }
4784 
4785     private class MyChangelistCallback
4786         extends HashMap<String, Collection<String>>
4787         implements ChangelistCallback
4788     {
4789         private static final long serialVersionUID = 1L;
4790 
4791         private HashSet<String> allChangelists = new HashSet<String>();
4792 
doChangelist(String path, String changelist)4793         public void doChangelist(String path, String changelist)
4794         {
4795             if (changelist != null)
4796                 allChangelists.add(changelist);
4797 
4798             path = fileToSVNPath(new File(path), true);
4799             if (super.containsKey(path))
4800             {
4801                 // Append the changelist to the existing list
4802                 Collection<String> changelists = super.get(path);
4803                 changelists.add(changelist);
4804             }
4805             else
4806             {
4807                 // Create a new changelist with that list
4808                 List<String> changelistList = new ArrayList<String>();
4809                 changelistList.add(changelist);
4810                 super.put(path, changelistList);
4811             }
4812         }
4813 
get(String path)4814         public Collection<String> get(String path)
4815         {
4816             return super.get(path);
4817         }
4818 
getChangelists()4819         public Collection<String> getChangelists()
4820         {
4821             return allChangelists;
4822         }
4823     }
4824 
4825     private class MyInfoCallback implements InfoCallback {
4826         private Info info;
4827 
singleInfo(Info info)4828         public void singleInfo(Info info) {
4829             this.info = info;
4830         }
4831 
getInfo()4832         public Info getInfo() {
4833             return info;
4834         }
4835     }
4836 
checkCommitRevision(OneTest thisTest, String failureMsg, long expectedRevision, Set<String> path, String message, Depth depth, boolean noUnlock, boolean keepChangelist, Collection<String> changelists, Map<String, String> revpropTable)4837     private void checkCommitRevision(OneTest thisTest, String failureMsg,
4838                                      long expectedRevision,
4839                                      Set<String> path, String message,
4840                                      Depth depth, boolean noUnlock,
4841                                      boolean keepChangelist,
4842                                      Collection<String> changelists,
4843                                      Map<String, String> revpropTable)
4844             throws ClientException
4845     {
4846         MyCommitCallback callback = new MyCommitCallback();
4847 
4848         client.commit(path, depth, noUnlock, keepChangelist,
4849                       changelists, revpropTable,
4850                       new ConstMsg(message), callback);
4851         assertEquals(failureMsg, callback.getRevision(), expectedRevision);
4852     }
4853 
4854     private class MyCommitCallback implements CommitCallback
4855     {
4856         private CommitInfo info = null;
4857 
commitInfo(CommitInfo info)4858         public void commitInfo(CommitInfo info) {
4859             this.info = info;
4860         }
4861 
getRevision()4862         public long getRevision() {
4863             if (info != null)
4864                 return info.getRevision();
4865             else
4866                 return -1;
4867         }
4868     }
4869 
4870     private class MyStatusCallback implements StatusCallback
4871     {
4872         private List<Status> statuses = new ArrayList<Status>();
4873 
doStatus(String path, Status status)4874         public void doStatus(String path, Status status)
4875         {
4876             if (status != null)
4877                 statuses.add(status);
4878         }
4879 
getStatusArray()4880         public Status[] getStatusArray()
4881         {
4882             return statuses.toArray(new Status[statuses.size()]);
4883         }
4884     }
4885 
4886     private class ConstMsg implements CommitMessageCallback
4887     {
4888         private String message;
4889 
ConstMsg(String message)4890         ConstMsg(String message)
4891         {
4892             this.message = message;
4893         }
4894 
getLogMessage(Set<CommitItem> items)4895         public String getLogMessage(Set<CommitItem> items)
4896         {
4897             return message;
4898         }
4899     }
4900 
collectProperties(String path, Revision revision, Revision pegRevision, Depth depth, Collection<String> changelists)4901     private Map<String, byte[]> collectProperties(String path,
4902                                              Revision revision,
4903                                              Revision pegRevision, Depth depth,
4904                                              Collection<String> changelists)
4905         throws ClientException
4906     {
4907        final Map<String, Map<String, byte[]>> propMap =
4908             new HashMap<String, Map<String, byte[]>>();
4909 
4910         client.properties(path, revision, revision, depth, changelists,
4911                 new ProplistCallback () {
4912             public void singlePath(String path, Map<String, byte[]> props)
4913             { propMap.put(path, props); }
4914         });
4915 
4916         return propMap.get(path);
4917     }
4918 
collectDirEntries(String url, Revision revision, Revision pegRevision, Depth depth, int direntFields, boolean fetchLocks)4919     private DirEntry[] collectDirEntries(String url, Revision revision,
4920                                          Revision pegRevision, Depth depth,
4921                                          int direntFields, boolean fetchLocks)
4922         throws ClientException
4923     {
4924         class MyListCallback implements ListCallback
4925         {
4926             private List<DirEntry> dirents = new ArrayList<DirEntry>();
4927 
4928             public void doEntry(DirEntry dirent, Lock lock)
4929             {
4930                 // All of this is meant to retain backward compatibility with
4931                 // the old svn_client_ls-style API.  For further information
4932                 // about what is going on here, see the comments in
4933                 // libsvn_client/list.c:store_dirent().
4934 
4935                 if (dirent.getPath().length() == 0)
4936                 {
4937                     if (dirent.getNodeKind() == NodeKind.file)
4938                     {
4939                         String absPath = dirent.getAbsPath();
4940                         int lastSeparator = absPath.lastIndexOf('/');
4941                         String path = absPath.substring(lastSeparator,
4942                                                         absPath.length());
4943                         dirent.setPath(path);
4944                     }
4945                     else
4946                     {
4947                         // It's the requested directory, which we don't want
4948                         // to add.
4949                         return;
4950                     }
4951                 }
4952 
4953                 dirents.add(dirent);
4954             }
4955 
4956             public DirEntry[] getDirEntryArray()
4957             {
4958                 return dirents.toArray(new DirEntry[dirents.size()]);
4959             }
4960         }
4961 
4962         MyListCallback callback = new MyListCallback();
4963         client.list(url, revision, pegRevision, depth, direntFields,
4964                     fetchLocks, callback);
4965         return callback.getDirEntryArray();
4966     }
4967 
collectInfos(String pathOrUrl, Revision revision, Revision pegRevision, Depth depth, Collection<String> changelists)4968     private Info[] collectInfos(String pathOrUrl, Revision revision,
4969                                  Revision pegRevision, Depth depth,
4970                                  Collection<String> changelists)
4971         throws ClientException
4972     {
4973        final List<Info> infos = new ArrayList<Info>();
4974 
4975        client.info(pathOrUrl, revision, pegRevision, depth,
4976                    true, true, false,
4977                    changelists, new InfoCallback () {
4978             public void singleInfo(Info info)
4979             { infos.add(info); }
4980         });
4981         return infos.toArray(new Info[infos.size()]);
4982     }
4983 
collectLogMessages(String path, Revision pegRevision, List<RevisionRange> revisionRanges, boolean stopOnCopy, boolean discoverPath, boolean includeMergedRevisions, long limit)4984     private LogMessage[] collectLogMessages(String path, Revision pegRevision,
4985                                             List<RevisionRange> revisionRanges,
4986                                             boolean stopOnCopy,
4987                                             boolean discoverPath,
4988                                             boolean includeMergedRevisions,
4989                                             long limit)
4990         throws ClientException
4991     {
4992         class MyLogMessageCallback implements LogMessageCallback
4993         {
4994             private List<LogMessage> messages = new ArrayList<LogMessage>();
4995 
4996             public void singleMessage(Set<ChangePath> changedPaths,
4997                                       long revision,
4998                                       Map<String, byte[]> revprops,
4999                                       boolean hasChildren)
5000             {
5001                 String author, message;
5002                 try {
5003                     author = new String(revprops.get("svn:author"), "UTF8");
5004                 } catch (UnsupportedEncodingException e) {
5005                     author = new String(revprops.get("svn:author"));
5006                 }
5007                 try {
5008                     message = new String(revprops.get("svn:log"), "UTF8");
5009                 } catch (UnsupportedEncodingException e) {
5010                     message = new String(revprops.get("svn:log"));
5011                 }
5012                 long timeMicros;
5013 
5014                 try {
5015                     LogDate date = new LogDate(new String(
5016                                                     revprops.get("svn:date")));
5017                     timeMicros = date.getTimeMicros();
5018                 } catch (ParseException ex) {
5019                     timeMicros = 0;
5020                 }
5021 
5022                 LogMessage msg = new LogMessage(changedPaths, revision,
5023                                                 author, timeMicros, message);
5024 
5025                 /* Filter out the SVN_INVALID_REVNUM message which pre-1.5
5026                    clients won't expect, nor understand. */
5027                 if (revision != Revision.SVN_INVALID_REVNUM)
5028                     messages.add(msg);
5029             }
5030 
5031             public LogMessage[] getMessages()
5032             {
5033                 return messages.toArray(new LogMessage[messages.size()]);
5034             }
5035         }
5036 
5037         MyLogMessageCallback callback = new MyLogMessageCallback();
5038         // Testing variant with allRevProps = true
5039         client.logMessages(path, pegRevision, revisionRanges, stopOnCopy,
5040                            discoverPath, includeMergedRevisions, null,
5041                            true, limit, callback);
5042         return callback.getMessages();
5043     }
5044 
5045     @SuppressWarnings("deprecation")
collectBlameLines(String path, Revision pegRevision, Revision revisionStart, Revision revisionEnd, boolean ignoreMimeType, boolean includeMergedRevisions)5046     private byte[] collectBlameLines(String path, Revision pegRevision,
5047                                      Revision revisionStart,
5048                                      Revision revisionEnd,
5049                                      boolean ignoreMimeType,
5050                                      boolean includeMergedRevisions)
5051         throws ClientException
5052     {
5053         BlameCallbackImpl callback = new BlameCallbackImpl();
5054         client.blame(path, pegRevision, revisionStart, revisionEnd,
5055                      ignoreMimeType, includeMergedRevisions, callback);
5056 
5057         StringBuffer sb = new StringBuffer();
5058         for (int i = 0; i < callback.numberOfLines(); i++)
5059         {
5060             BlameCallbackImpl.BlameLine line = callback.getBlameLine(i);
5061             if (line != null)
5062             {
5063                 sb.append(line.toString());
5064                 sb.append("\n");
5065             }
5066         }
5067         return sb.toString().getBytes();
5068     }
5069 
5070     protected class LogMessage
5071     {
5072         private String message;
5073 
5074         private long timeMicros;
5075 
5076         private Date date;
5077 
5078         private long revision;
5079 
5080         private String author;
5081 
5082         private Set<ChangePath> changedPaths;
5083 
LogMessage(Set<ChangePath> cp, long r, String a, long t, String m)5084         LogMessage(Set<ChangePath> cp, long r, String a, long t, String m)
5085         {
5086             changedPaths = cp;
5087             revision = r;
5088             author = a;
5089             timeMicros = t;
5090             date = null;
5091             message = m;
5092         }
5093 
getMessage()5094         public String getMessage()
5095         {
5096             return message;
5097         }
5098 
getTimeMicros()5099         public long getTimeMicros()
5100         {
5101             return timeMicros;
5102         }
5103 
getTimeMillis()5104         public long getTimeMillis()
5105         {
5106             return timeMicros / 1000;
5107         }
5108 
getDate()5109         public Date getDate()
5110         {
5111             if (date == null)
5112                date = new Date(timeMicros / 1000);
5113             return date;
5114         }
5115 
getRevisionNumber()5116         public long getRevisionNumber()
5117         {
5118             return revision;
5119         }
5120 
getAuthor()5121         public String getAuthor()
5122         {
5123             return author;
5124         }
5125 
getChangedPaths()5126         public Set<ChangePath> getChangedPaths()
5127         {
5128             return changedPaths;
5129         }
5130     }
5131 
5132     /* A blame callback implementation. */
5133     @SuppressWarnings("deprecation")
5134     protected class BlameCallbackImpl implements BlameCallback
5135     {
5136 
5137         /** list of blame records (lines) */
5138         private List<BlameLine> lines = new ArrayList<BlameLine>();
5139 
singleLine(Date changed, long revision, String author, String line)5140         public void singleLine(Date changed, long revision, String author,
5141                                String line)
5142         {
5143             addBlameLine(new BlameLine(revision, author, changed, line));
5144         }
5145 
singleLine(Date date, long revision, String author, Date merged_date, long merged_revision, String merged_author, String merged_path, String line)5146         public void singleLine(Date date, long revision, String author,
5147                                Date merged_date, long merged_revision,
5148                                String merged_author, String merged_path,
5149                                String line)
5150         {
5151             addBlameLine(new BlameLine(getRevision(revision, merged_revision),
5152                                        getAuthor(author, merged_author),
5153                                        getDate(date, merged_date),
5154                                        line));
5155         }
5156 
singleLine(long lineNum, long rev, Map<String, byte[]> revProps, long mergedRevision, Map<String, byte[]> mergedRevProps, String mergedPath, String line, boolean localChange)5157         public void singleLine(long lineNum, long rev,
5158                                Map<String, byte[]> revProps,
5159                                long mergedRevision,
5160                                Map<String, byte[]> mergedRevProps,
5161                                String mergedPath, String line,
5162                                boolean localChange)
5163             throws ClientException
5164         {
5165             DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
5166 
5167             try {
5168                 singleLine(
5169                     df.parse(new String(revProps.get("svn:date"))),
5170                     rev,
5171                     new String(revProps.get("svn:author")),
5172                     mergedRevProps == null ? null
5173                         : df.parse(new String(mergedRevProps.get("svn:date"))),
5174                     mergedRevision,
5175                     mergedRevProps == null ? null
5176                         : new String(mergedRevProps.get("svn:author")),
5177                     mergedPath, line);
5178             } catch (ParseException e) {
5179                 throw ClientException.fromException(e);
5180             }
5181         }
5182 
getDate(Date date, Date merged_date)5183         private Date getDate(Date date, Date merged_date) {
5184             return (merged_date == null ? date : merged_date);
5185         }
5186 
getAuthor(String author, String merged_author)5187         private String getAuthor(String author, String merged_author) {
5188             return (merged_author == null ? author : merged_author);
5189         }
5190 
getRevision(long revision, long merged_revision)5191         private long getRevision(long revision, long merged_revision) {
5192             return (merged_revision == -1 ? revision : merged_revision);
5193         }
5194 
5195         /**
5196          * Retrieve the number of line of blame information
5197          * @return number of lines of blame information
5198          */
numberOfLines()5199         public int numberOfLines()
5200         {
5201             return this.lines.size();
5202         }
5203 
5204         /**
5205          * Retrieve blame information for specified line number
5206          * @param i the line number to retrieve blame information about
5207          * @return  Returns object with blame information for line
5208          */
getBlameLine(int i)5209         public BlameLine getBlameLine(int i)
5210         {
5211             if (i >= this.lines.size())
5212             {
5213                 return null;
5214             }
5215             return this.lines.get(i);
5216         }
5217 
5218         /**
5219          * Append the given blame info to the list
5220          * @param blameLine
5221          */
addBlameLine(BlameLine blameLine)5222         protected void addBlameLine(BlameLine blameLine)
5223         {
5224             this.lines.add(blameLine);
5225         }
5226 
5227         /**
5228          * Class represeting one line of the lines, i.e. a blame record
5229          *
5230          */
5231         public class BlameLine
5232         {
5233 
5234             private long revision;
5235 
5236             private String author;
5237 
5238             private Date changed;
5239 
5240             private String line;
5241 
5242             /**
5243              * Constructor
5244              *
5245              * @param revision
5246              * @param author
5247              * @param changed
5248              * @param line
5249              */
BlameLine(long revision, String author, Date changed, String line)5250             public BlameLine(long revision, String author,
5251                              Date changed, String line)
5252             {
5253                 super();
5254                 this.revision = revision;
5255                 this.author = author;
5256                 this.changed = changed;
5257                 this.line = line;
5258             }
5259 
5260             /**
5261              * @return Returns the author.
5262              */
getAuthor()5263             public String getAuthor()
5264             {
5265                 return author;
5266             }
5267 
5268             /**
5269              * @return Returns the date changed.
5270              */
getChanged()5271             public Date getChanged()
5272             {
5273                 return changed;
5274             }
5275 
5276             /**
5277              * @return Returns the source line content.
5278              */
getLine()5279             public String getLine()
5280             {
5281                 return line;
5282             }
5283 
5284 
5285             /**
5286              * @return Returns the revision.
5287              */
getRevision()5288             public long getRevision()
5289             {
5290                 return revision;
5291             }
5292 
5293             /*
5294              * (non-Javadoc)
5295              * @see java.lang.Object#toString()
5296              */
toString()5297             public String toString()
5298             {
5299                 StringBuffer sb = new StringBuffer();
5300                 if (revision > 0)
5301                 {
5302                     pad(sb, Long.toString(revision), 6);
5303                     sb.append(' ');
5304                 }
5305                 else
5306                 {
5307                     sb.append("     - ");
5308                 }
5309 
5310                 if (author != null)
5311                 {
5312                     pad(sb, author, 10);
5313                     sb.append(" ");
5314                 }
5315                 else
5316                 {
5317                     sb.append("         - ");
5318                 }
5319 
5320                 sb.append(line);
5321 
5322                 return sb.toString();
5323             }
5324 
5325             /**
5326              * Left pad the input string to a given length, to simulate
5327              * printf()-style output. This method appends the output to the
5328              * class sb member.
5329              * @param sb StringBuffer to append to
5330              * @param val the input string
5331              * @param len the minimum length to pad to
5332              */
pad(StringBuffer sb, String val, int len)5333             private void pad(StringBuffer sb, String val, int len)
5334             {
5335                 int padding = len - val.length();
5336 
5337                 for (int i = 0; i < padding; i++)
5338                 {
5339                     sb.append(' ');
5340                 }
5341 
5342                 sb.append(val);
5343             }
5344         }
5345     }
5346 
5347     /* A blame range callback implementation. */
5348     protected class BlameRangeCallbackImpl implements BlameRangeCallback
5349     {
5350         public long startRevnum = -1;
5351         public long endRevnum = -1;
setRange(long start, long end)5352         public void setRange(long start, long end)
5353         {
5354             startRevnum = start;
5355             endRevnum = end;
5356         }
5357     }
5358 
5359     /* A blame line callback implementation. */
5360     protected class BlameLineCallbackImpl implements BlameLineCallback
5361     {
5362 
5363         /** list of blame records (lines) */
5364         private List<BlameLine> lines = new ArrayList<BlameLine>();
5365 
singleLine(long lineNum, long rev, Map<String, byte[]> revProps, long mergedRevision, Map<String, byte[]> mergedRevProps, String mergedPath, boolean localChange, byte[] line)5366         public void singleLine(long lineNum, long rev,
5367                                Map<String, byte[]> revProps,
5368                                long mergedRevision,
5369                                Map<String, byte[]> mergedRevProps,
5370                                String mergedPath, boolean localChange,
5371                                byte[] line)
5372             throws ClientException
5373         {
5374             DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
5375 
5376             try {
5377                 insertLine(
5378                     df.parse(new String(revProps.get("svn:date"))),
5379                     rev,
5380                     new String(revProps.get("svn:author")),
5381                     mergedRevProps == null ? null
5382                         : df.parse(new String(mergedRevProps.get("svn:date"))),
5383                     mergedRevision,
5384                     mergedRevProps == null ? null
5385                         : new String(mergedRevProps.get("svn:author")),
5386                     mergedPath, line);
5387             } catch (ParseException e) {
5388                 throw ClientException.fromException(e);
5389             }
5390         }
5391 
getDate(Date date, Date merged_date)5392         private Date getDate(Date date, Date merged_date) {
5393             return (merged_date == null ? date : merged_date);
5394         }
5395 
getAuthor(String author, String merged_author)5396         private String getAuthor(String author, String merged_author) {
5397             return (merged_author == null ? author : merged_author);
5398         }
5399 
getRevision(long revision, long merged_revision)5400         private long getRevision(long revision, long merged_revision) {
5401             return (merged_revision == -1 ? revision : merged_revision);
5402         }
5403 
insertLine(Date date, long revision, String author, Date merged_date, long merged_revision, String merged_author, String merged_path, byte[] line)5404         private void insertLine(Date date, long revision, String author,
5405                                 Date merged_date, long merged_revision,
5406                                 String merged_author, String merged_path,
5407                                 byte[] line)
5408         {
5409             this.lines.add(new BlameLine(getRevision(revision, merged_revision),
5410                                          getAuthor(author, merged_author),
5411                                          getDate(date, merged_date),
5412                                          line));
5413         }
5414 
5415         /**
5416          * Retrieve the number of line of blame information
5417          * @return number of lines of blame information
5418          */
numberOfLines()5419         public int numberOfLines()
5420         {
5421             return this.lines.size();
5422         }
5423 
5424         /**
5425          * Retrieve blame information for specified line number
5426          * @param i the line number to retrieve blame information about
5427          * @return  Returns object with blame information for line
5428          */
getBlameLine(int i)5429         public BlameLine getBlameLine(int i)
5430         {
5431             if (i >= this.lines.size())
5432             {
5433                 return null;
5434             }
5435             return this.lines.get(i);
5436         }
5437 
5438         /**
5439          * Class represeting one line of the lines, i.e. a blame record
5440          */
5441         public final class BlameLine
5442         {
5443             private long revision;
5444             private String author;
5445             private Date changed;
5446             private byte[] line;
5447 
5448             /**
5449              * Constructor
5450              *
5451              * @param revision
5452              * @param author
5453              * @param changed
5454              * @param line
5455              */
BlameLine(long revision, String author, Date changed, byte[] line)5456             public BlameLine(long revision, String author,
5457                              Date changed, byte[] line)
5458             {
5459                 this.revision = revision;
5460                 this.author = author;
5461                 this.changed = changed;
5462                 this.line = line;
5463             }
5464 
5465             /**
5466              * @return Returns the author.
5467              */
getAuthor()5468             public String getAuthor()
5469             {
5470                 return author;
5471             }
5472 
5473             /**
5474              * @return Returns the date changed.
5475              */
getChanged()5476             public Date getChanged()
5477             {
5478                 return changed;
5479             }
5480 
5481             /**
5482              * @return Returns the source line content.
5483              */
getLine()5484             public byte[] getLine()
5485             {
5486                 return line;
5487             }
5488 
5489             /**
5490              * @return Returns the revision.
5491              */
getRevision()5492             public long getRevision()
5493             {
5494                 return revision;
5495             }
5496         }
5497     }
5498 
5499     /** A helper which calls update with a bunch of default args. */
update(OneTest thisTest)5500     private long update(OneTest thisTest)
5501         throws ClientException
5502     {
5503         return client.update(thisTest.getWCPathSet(), null,
5504                              Depth.unknown, false, false, false, false)[0];
5505     }
5506 
5507     /** A helper which calls update with a bunch of default args. */
update(OneTest thisTest, String subpath)5508     private long update(OneTest thisTest, String subpath)
5509         throws ClientException
5510     {
5511         return client.update(thisTest.getWCPathSet(subpath), null,
5512                              Depth.unknown, false, false, false, false)[0];
5513     }
5514 
setprop(String path, String name, String value)5515     private void setprop(String path, String name, String value)
5516         throws ClientException
5517     {
5518         Set<String> paths = new HashSet<String>();
5519         paths.add(path);
5520 
5521         client.propertySetLocal(paths, name,
5522                                 value != null ? value.getBytes() : null,
5523                                 Depth.empty, null, false);
5524     }
5525 
setprop(String path, String name, byte[] value)5526     private void setprop(String path, String name, byte[] value)
5527         throws ClientException
5528     {
5529         Set<String> paths = new HashSet<String>();
5530         paths.add(path);
5531 
5532         client.propertySetLocal(paths, name, value, Depth.empty,
5533                                 null, false);
5534     }
5535 
commit(OneTest thisTest, String msg)5536     private long commit(OneTest thisTest, String msg)
5537         throws ClientException
5538     {
5539         MyCommitCallback commitCallback = new MyCommitCallback();
5540 
5541         client.commit(thisTest.getWCPathSet(), Depth.infinity,
5542                       false, false, null, null, new ConstMsg(msg),
5543                       commitCallback);
5544         return commitCallback.getRevision();
5545     }
5546 }
5547