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