├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── index.js ├── package-lock.json ├── package.json └── test ├── commands.js └── createLocalSvnRepo.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /npm-debug.log 3 | /temp 4 | /api.md 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | cache: npm 5 | install: 6 | - npm install jshint -g 7 | - npm install 8 | script: 9 | - jshint index.js 10 | - npm test 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 peteward44 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-svn-ultimate 2 | 3 | ***This project is no longer maintained*** 4 | 5 | The ultimate SVN wrapper for node. Contains all the methods exposed by the command line svn tool, including checkout, update, info, etc, and includes svnmucc support. 6 | 7 | Has methods for manipulating both working copies and the repo directly. 8 | 9 | All direct svn command line functions are exposed through the commands object, and accept the same parameters as the command line tool. 10 | 11 | Utility methods are provided through a util object. 12 | 13 | ``` 14 | npm install node-svn-ultimate --save 15 | ``` 16 | 17 | ### Example usage 18 | 19 | ```js 20 | var svnUltimate = require('node-svn-ultimate'); 21 | 22 | svnUltimate.commands.checkout( 'https://my.url/svn/repo', '/home/user/checkout', function( err ) { 23 | console.log( "Checkout complete" ); 24 | } ); 25 | 26 | svnUltimate.commands.update( '/home/user/checkout', 27 | { // optional options object - can be passed to any command not just update 28 | trustServerCert: true, // same as --trust-server-cert 29 | username: "username", // same as --username 30 | password: "password", // same as --password 31 | shell: "sh", // override shell used to execute command 32 | cwd: process.cwd(), // override working directory command is executed 33 | quiet: true, // provide --quiet to commands that accept it 34 | force: true, // provide --force to commands that accept it 35 | revision: 33050, // provide --revision to commands that accept it 36 | depth: "empty", // provide --depth to commands that accept it 37 | ignoreExternals: true, // provide --ignore-externals to commands that accept it 38 | params: [ '-m "Commit comment"' ], // extra parameters to pass 39 | 'config-option': [ 40 | 'servers:global:http-proxy-host=proxy.someProxy.com', 41 | 'servers:global:http-proxy-port=8080', 42 | ] // provide --config-option to commands that accept it. Use an array for multiple config options 43 | }, 44 | function( err ) { 45 | console.log( "Update complete" ); 46 | } ); 47 | 48 | ``` 49 | 50 | ### Utility methods 51 | 52 | ```js 53 | // Gets the working copy revision or the HEAD revision if the target is a URL 54 | svnUltimate.util.getRevision( 'https://my.url/svn/repo', function( err, revision ) { 55 | console.log( "Head revision=" + revision ); 56 | } ); 57 | 58 | var obj = svnUltimate.util.parseUrl( 'https://my.url/svn/repo/trunk' ); 59 | // this call will return an object comprising of 60 | obj = { 61 | rootUrl: 'https://my.url/svn/repo', 62 | type: 'trunk', // either trunk, tags, or branches 63 | typeName: '1.3.5' // only populated if a tag or a branch, name of the tag or branch 64 | trunkUrl: 'https://my.url/svn/repo/trunk', 65 | tagsUrl: 'https://my.url/svn/repo/tags', 66 | branchesUrl: 'https://my.url/svn/repo/branches' 67 | }; 68 | 69 | 70 | svnUltimate.util.getTags( 'https://my.url/svn/repo/trunk', function( err, tagsArray ) { 71 | // tagsArray will be an array of strings containing all tag names 72 | } ); 73 | 74 | svnUltimate.util.getLatestTag( 'https://my.url/svn/repo/trunk', function( err, latestTag ) { 75 | // latestTag will be the most recent tag, worked out by semver comparison (not the date it was created) 76 | } ); 77 | 78 | ``` 79 | 80 | ## Methods 81 | 82 |
83 |
commands : object
84 |

Exposes the commands for the command line svn tool.

85 |
86 |
util : object
87 |

Exposes some custom utility methods

88 |
89 |
90 | 91 | 92 | ## commands : object 93 | 94 | Exposes the commands for the command line svn tool. 95 | 96 | **Kind**: global namespace 97 | 98 | * [commands](#commands) : object 99 | * [.checkout(url, dir, [options], [callback])](#commands.checkout) 100 | * [.add(files, [options], [callback])](#commands.add) 101 | * [.cat(targets, [options], [callback])](#commands.cat) 102 | * [.cleanup(wc, [options], [callback])](#commands.cleanup) 103 | * [.commit(files, [options], [callback])](#commands.commit) 104 | * [.copy(srcs, dst, [options], [callback])](#commands.copy) 105 | * [.del(srcs, [options], [callback])](#commands.del) 106 | * [.export(src, dst, [options], [callback])](#commands.export) 107 | * [.import(src, dst, [options], [callback])](#commands.import) 108 | * [.info(targets, [options], [callback])](#commands.info) 109 | * [.list(targets, [options], [callback])](#commands.list) 110 | * [.lock(targets, [options], [callback])](#commands.lock) 111 | * [.log(targets, [options], [callback])](#commands.log) 112 | * [.merge(targets, [options], [callback])](#commands.merge) 113 | * [.mergeinfo(source, target, [options], [callback])](#commands.mergeinfo) 114 | * [.mkdir(targets, [options], [callback])](#commands.mkdir) 115 | * [.move(srcs, dst, [options], [callback])](#commands.move) 116 | * [.propdel(propName, target, [options], [callback])](#commands.propdel) 117 | * [.propget(propName, targets, [options], [callback])](#commands.propget) 118 | * [.proplist(targets, [options], [callback])](#commands.proplist) 119 | * [.propset(propName, propVal, wc, [options], [callback])](#commands.propset) 120 | * [.relocate(url, wc, [options], [callback])](#commands.relocate) 121 | * [.revert(wc, [options], [callback])](#commands.revert) 122 | * [.status(wc, [options], [callback])](#commands.status) 123 | * [.switch(url, wc, [options], [callback])](#commands.switch) 124 | * [.unlock(targets, [options], [callback])](#commands.unlock) 125 | * [.update(wcs, [options], [callback])](#commands.update) 126 | * [.upgrade(wcs, [options], [callback])](#commands.upgrade) 127 | * [.mucc(commandArray, commitMessage, [options], [callback])](#commands.mucc) 128 | 129 | 130 | ### commands.checkout(url, dir, [options], [callback]) 131 | Checks out a repository to a working copy 132 | 133 | **Kind**: static method of [commands](#commands) 134 | 135 | | Param | Type | Description | 136 | | --- | --- | --- | 137 | | url | string | Repository URL | 138 | | dir | string | Working copy dir | 139 | | [options] | object | Options object | 140 | | [callback] | function | Complete callback | 141 | 142 | 143 | ### commands.add(files, [options], [callback]) 144 | Adds a file / folder to a working copy 145 | 146 | **Kind**: static method of [commands](#commands) 147 | 148 | | Param | Type | Description | 149 | | --- | --- | --- | 150 | | files | Array | string | Add given files / folders | 151 | | [options] | object | Options object | 152 | | [callback] | function | Complete callback | 153 | 154 | 155 | ### commands.cat(targets, [options], [callback]) 156 | Gets the content of a file from either a working copy or a URL. 157 | 158 | **Kind**: static method of [commands](#commands) 159 | 160 | | Param | Type | Description | 161 | | --- | --- | --- | 162 | | targets | Array | string | Array of URLs or working copy files to catalogue | 163 | | [options] | object | Options object | 164 | | [callback] | function | Complete callback | 165 | 166 | 167 | ### commands.cleanup(wc, [options], [callback]) 168 | Performs an svn cleanup operation on the working copy 169 | 170 | **Kind**: static method of [commands](#commands) 171 | 172 | | Param | Type | Description | 173 | | --- | --- | --- | 174 | | wc | string | Working copy directory to clean | 175 | | [options] | object | Options object | 176 | | [callback] | function | Complete callback | 177 | 178 | 179 | ### commands.commit(files, [options], [callback]) 180 | Commits a working copy to a repository 181 | 182 | **Kind**: static method of [commands](#commands) 183 | 184 | | Param | Type | Description | 185 | | --- | --- | --- | 186 | | files | Array | string | Array of files / folders to commit | 187 | | [options] | object | Options object | 188 | | [callback] | function | Complete callback | 189 | 190 | 191 | ### commands.copy(srcs, dst, [options], [callback]) 192 | Copies a file / folder within either a working copy or a URL 193 | 194 | **Kind**: static method of [commands](#commands) 195 | 196 | | Param | Type | Description | 197 | | --- | --- | --- | 198 | | srcs | Array | string | URLs / files to copy | 199 | | dst | string | destination | 200 | | [options] | object | Options object | 201 | | [callback] | function | Complete callback | 202 | 203 | 204 | ### commands.del(srcs, [options], [callback]) 205 | Deletes a file/folder from either a working copy or a URL 206 | 207 | **Kind**: static method of [commands](#commands) 208 | 209 | | Param | Type | Description | 210 | | --- | --- | --- | 211 | | srcs | Array | string | Array of URLs / files to delete | 212 | | [options] | object | Options object | 213 | | [callback] | function | Complete callback | 214 | 215 | 216 | ### commands.export(src, dst, [options], [callback]) 217 | Exports a file from the repository to a local file 218 | 219 | **Kind**: static method of [commands](#commands) 220 | 221 | | Param | Type | Description | 222 | | --- | --- | --- | 223 | | src | string | Source URL | 224 | | dst | string | Destination file | 225 | | [options] | object | Options object | 226 | | [callback] | function | Complete callback | 227 | 228 | 229 | ### commands.import(src, dst, [options], [callback]) 230 | Imports a file to the repository 231 | 232 | **Kind**: static method of [commands](#commands) 233 | 234 | | Param | Type | Description | 235 | | --- | --- | --- | 236 | | src | string | Source file | 237 | | dst | string | Destination URL | 238 | | [options] | object | Options object | 239 | | [callback] | function | Complete callback | 240 | 241 | 242 | ### commands.info(targets, [options], [callback]) 243 | Performs an svn info command on a given working copy file / URL 244 | 245 | **Kind**: static method of [commands](#commands) 246 | 247 | | Param | Type | Description | 248 | | --- | --- | --- | 249 | | targets | Array | string | Target URLs / files to info | 250 | | [options] | object | Options object | 251 | | [callback] | function | Complete callback | 252 | 253 | 254 | ### commands.list(targets, [options], [callback]) 255 | Lists the files within a directory, either working copy or URL 256 | 257 | **Kind**: static method of [commands](#commands) 258 | 259 | | Param | Type | Description | 260 | | --- | --- | --- | 261 | | targets | Array | string | Target URLs / files to list | 262 | | [options] | object | Options object | 263 | | [callback] | function | Complete callback | 264 | 265 | 266 | ### commands.lock(targets, [options], [callback]) 267 | Locks a file in a working copy / repository 268 | 269 | **Kind**: static method of [commands](#commands) 270 | 271 | | Param | Type | Description | 272 | | --- | --- | --- | 273 | | targets | Array | string | Target URLs / files to lock | 274 | | [options] | object | Options object | 275 | | [callback] | function | Complete callback | 276 | 277 | 278 | ### commands.log(targets, [options], [callback]) 279 | Gets the SVN message log and returns as a JSON object 280 | 281 | **Kind**: static method of [commands](#commands) 282 | 283 | | Param | Type | Description | 284 | | --- | --- | --- | 285 | | targets | Array | string | Target URLs / files to get logs for | 286 | | [options] | object | Options object | 287 | | [callback] | function | Complete callback | 288 | 289 | 290 | ### commands.merge(targets, [options], [callback]) 291 | Apply the differences between two sources to a working copy path 292 | 293 | **Kind**: static method of [commands](#commands) 294 | 295 | | Param | Type | Description | 296 | | --- | --- | --- | 297 | | targets | Array | string | Target URLs | 298 | | [options] | object | Options object | 299 | | [callback] | function | Complete callback | 300 | 301 | 302 | 303 | ### commands.mergeinfo(source, target, [options], [callback]) 304 | Query information related to merges (or potential merges) between SOURCE and TARGET. 305 | 306 | **Kind**: static method of [commands](#commands) 307 | 308 | | Param | Type | Description | 309 | | --- | --- | --- | 310 | | source | string | SOURCE URL | 311 | | target | string | TARGET URL | 312 | | [options] | object | Options object | 313 | | [callback] | function | Complete callback | 314 | 315 | 316 | ### commands.mkdir(targets, [options], [callback]) 317 | Creates a directory in the working copy or repository 318 | 319 | **Kind**: static method of [commands](#commands) 320 | 321 | | Param | Type | Description | 322 | | --- | --- | --- | 323 | | targets | Array | string | Target URLs / folders to create | 324 | | [options] | object | Options object | 325 | | [callback] | function | Complete callback | 326 | 327 | 328 | ### commands.move(srcs, dst, [options], [callback]) 329 | Moves a file / folder in a working copy or URL 330 | 331 | **Kind**: static method of [commands](#commands) 332 | 333 | | Param | Type | Description | 334 | | --- | --- | --- | 335 | | srcs | Array | string | Target URLs / files to move | 336 | | dst | string | Destination URL / file | 337 | | [options] | object | Options object | 338 | | [callback] | function | Complete callback | 339 | 340 | 341 | ### commands.propdel(propName, target, [options], [callback]) 342 | Deletes an svn property from a working copy / repository 343 | 344 | **Kind**: static method of [commands](#commands) 345 | 346 | | Param | Type | Description | 347 | | --- | --- | --- | 348 | | propName | string | Property name | 349 | | target | string | Target file / folder or URL | 350 | | [options] | object | Options object | 351 | | [callback] | function | Complete callback | 352 | 353 | 354 | ### commands.propget(propName, targets, [options], [callback]) 355 | Gets an svn property from a working copy / repository 356 | 357 | **Kind**: static method of [commands](#commands) 358 | 359 | | Param | Type | Description | 360 | | --- | --- | --- | 361 | | propName | string | Property name | 362 | | targets | Array | string | Target file / folder or URL | 363 | | [options] | object | Options object | 364 | | [callback] | function | Complete callback | 365 | 366 | 367 | ### commands.proplist(targets, [options], [callback]) 368 | Lists svn properties from a working copy / repository 369 | 370 | **Kind**: static method of [commands](#commands) 371 | 372 | | Param | Type | Description | 373 | | --- | --- | --- | 374 | | targets | Array | string | Target file / folder or URL | 375 | | [options] | object | Options object | 376 | | [callback] | function | Complete callback | 377 | 378 | 379 | ### commands.propset(propName, propVal, wc, [options], [callback]) 380 | Sets an svn property from a working copy / repository 381 | 382 | **Kind**: static method of [commands](#commands) 383 | 384 | | Param | Type | Description | 385 | | --- | --- | --- | 386 | | propName | string | Property name | 387 | | propVal | string | Property value | 388 | | wc | string | Target file / folder or URL | 389 | | [options] | object | Options object | 390 | | [callback] | function | Complete callback | 391 | 392 | 393 | ### commands.relocate(url, wc, [options], [callback]) 394 | Relocates an svn working copy 395 | 396 | **Kind**: static method of [commands](#commands) 397 | 398 | | Param | Type | Description | 399 | | --- | --- | --- | 400 | | url | string | Relocation URL | 401 | | wc | string | Working copy to relocate | 402 | | [options] | object | Options object | 403 | | [callback] | function | Complete callback | 404 | 405 | 406 | ### commands.revert(wc, [options], [callback]) 407 | Reverts files / folders in a working copy to their uncommited state 408 | 409 | **Kind**: static method of [commands](#commands) 410 | 411 | | Param | Type | Description | 412 | | --- | --- | --- | 413 | | wc | string | Working copy target | 414 | | [options] | object | Options object | 415 | | [callback] | function | Complete callback | 416 | 417 | 418 | ### commands.status(wc, [options], [callback]) 419 | Performs an svn status command on a working copy 420 | 421 | **Kind**: static method of [commands](#commands) 422 | 423 | | Param | Type | Description | 424 | | --- | --- | --- | 425 | | wc | string | Working copy target | 426 | | [options] | object | Options object | 427 | | [callback] | function | Complete callback | 428 | 429 | 430 | ### commands.switch(url, wc, [options], [callback]) 431 | Switches to a given branch / tag for a working copy 432 | 433 | **Kind**: static method of [commands](#commands) 434 | 435 | | Param | Type | Description | 436 | | --- | --- | --- | 437 | | url | string | Switch URL | 438 | | wc | string | Working copy target | 439 | | [options] | object | Options object | 440 | | [callback] | function | Complete callback | 441 | 442 | 443 | ### commands.unlock(targets, [options], [callback]) 444 | Unlocks a previously locked svn file from a working copy / repository 445 | 446 | **Kind**: static method of [commands](#commands) 447 | 448 | | Param | Type | Description | 449 | | --- | --- | --- | 450 | | targets | Array | string | Working copy / URL targets | 451 | | [options] | object | Options object | 452 | | [callback] | function | Complete callback | 453 | 454 | 455 | ### commands.update(wcs, [options], [callback]) 456 | Updates an svn working copy 457 | 458 | **Kind**: static method of [commands](#commands) 459 | 460 | | Param | Type | Description | 461 | | --- | --- | --- | 462 | | wcs | Array | string | Working copy targets | 463 | | [options] | object | Options object | 464 | | [callback] | function | Complete callback | 465 | 466 | 467 | ### commands.upgrade(wcs, [options], [callback]) 468 | Upgrades a given svn working copy (requires v1.7 of svn client) 469 | 470 | **Kind**: static method of [commands](#commands) 471 | 472 | | Param | Type | Description | 473 | | --- | --- | --- | 474 | | wcs | Array | string | Working copy targets | 475 | | [options] | object | Options object | 476 | | [callback] | function | Complete callback | 477 | 478 | 479 | ### commands.mucc(commandArray, commitMessage, [options], [callback]) 480 | Executes svnmucc command, for multiple commands 481 | 482 | **Kind**: static method of [commands](#commands) 483 | **See**: http://svnbook.red-bean.com/en/1.8/svn.ref.svnmucc.re.html 484 | 485 | | Param | Type | Description | 486 | | --- | --- | --- | 487 | | commandArray | Array | Array of command strings, see above link for options | 488 | | commitMessage | string | Commit message to use | 489 | | [options] | object | Options object | 490 | | [callback] | function | Complete callback | 491 | 492 | 493 | ## util : object 494 | Exposes some custom utility methods 495 | 496 | **Kind**: global namespace 497 | 498 | * [util](#util) : object 499 | * [.getRevision(target, [options], [callback])](#util.getRevision) 500 | * [.getWorkingCopyRevision(wcDir, [options], [callback])](#util.getWorkingCopyRevision) 501 | * [.parseUrl(url)](#util.parseUrl) ? object 502 | * [.getTags(url, [options], [callback])](#util.getTags) 503 | * [.getLatestTag(url, options, [callback])](#util.getLatestTag) 504 | 505 | 506 | ### util.getRevision(target, [options], [callback]) 507 | Gets head revision of a given URL 508 | 509 | **Kind**: static method of [util](#util) 510 | 511 | | Param | Type | Description | 512 | | --- | --- | --- | 513 | | target | string | Target URL | 514 | | [options] | object | Options object | 515 | | [callback] | function | Complete callback | 516 | 517 | 518 | ### util.getWorkingCopyRevision(wcDir, [options], [callback]) 519 | Gets the revision of a working copy. 520 | 521 | **Kind**: static method of [util](#util) 522 | 523 | | Param | Type | Description | 524 | | --- | --- | --- | 525 | | wcDir | string | Working copy folder | 526 | | [options] | object | Options object | 527 | | [callback] | function | Complete callback | 528 | 529 | 530 | ### util.parseUrl(url) ? object 531 | Parse a url for an SVN project repository and breaks it apart 532 | 533 | **Kind**: static method of [util](#util) 534 | 535 | | Param | Type | Description | 536 | | --- | --- | --- | 537 | | url | string | URL to parse | 538 | 539 | 540 | ### util.getTags(url, [options], [callback]) 541 | Gets all available tags for the given svn URL 542 | 543 | **Kind**: static method of [util](#util) 544 | 545 | | Param | Type | Description | 546 | | --- | --- | --- | 547 | | url | string | Project URL to get tags for | 548 | | [options] | object | Options object | 549 | | [callback] | function | Complete callback | 550 | 551 | 552 | ### util.getLatestTag(url, options, [callback]) 553 | Uses node's semver package to work out the latest tag value 554 | 555 | **Kind**: static method of [util](#util) 556 | 557 | | Param | Type | Description | 558 | | --- | --- | --- | 559 | | url | string | Project URL to get latest tag for | 560 | | options | object | Options object | 561 | | [callback] | function | Complete callback | 562 | 563 | 564 | ### util.getBranches(url, [options], [callback]) 565 | Gets all available branches for the given svn URL 566 | 567 | **Kind**: static method of [util](#util) 568 | 569 | | Param | Type | Description | 570 | | --- | --- | --- | 571 | | url | string | Project URL to get branches for | 572 | | [options] | object | Options object | 573 | | [callback] | function | Complete callback | 574 | 575 | 576 | 577 | 578 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | var exec = require( 'child_process' ).exec; 5 | var fs = require( 'fs-extra' ); 6 | var os = require( 'os' ); 7 | var path = require( 'path' ); 8 | var uuid = require( 'uuid' ); 9 | var xml2js = require( 'xml2js' ); 10 | var semver = require( 'semver' ); 11 | 12 | 13 | 14 | var xmlToJson = function( dataXml, callback ) { 15 | xml2js.parseString(dataXml, 16 | { 17 | explicitRoot: false, 18 | explicitArray: false 19 | }, 20 | function( err, json ) { callback( err, json ); } 21 | ); 22 | }; 23 | 24 | var execute = function( cmd, options, callback ) { 25 | 26 | options = options || {}; 27 | if ( options.shell === undefined && os.platform === "win32" ) { 28 | options.shell = 'start "" /B '; // windows only - this makes it so a cmd window won't pop up when running as a service or through pm2 29 | } 30 | options.cwd = options.cwd || process.cwd(); 31 | var execOptions = { 32 | cwd: options.cwd, 33 | shell: options.shell, 34 | maxBuffer: options.maxBuffer || ( 5 * 1024 * 1024 ) // defaults to 5MB 35 | }; 36 | 37 | if ( !options.noStandardOptions ) { 38 | cmd += ' --non-interactive'; 39 | if ( options.trustServerCert ) { 40 | cmd += ' --trust-server-cert'; 41 | } 42 | if ( typeof options.username === 'string' ) { 43 | cmd += ' --username=' + options.username; 44 | } 45 | if ( typeof options.password === 'string' ) { 46 | cmd += ' --password=' + options.password; 47 | } 48 | if ( options['config-option'] ) { 49 | if ( Array.isArray(options['config-option']) ) { 50 | options['config-option'].forEach( function( option ) { 51 | cmd += ' --config-option=' + option; 52 | }); 53 | } else { 54 | cmd += ' --config-option=' + options['config-option']; 55 | } 56 | } 57 | } 58 | if ( options.params ) { 59 | cmd += ' ' + options.params.join( " " ); 60 | } 61 | exec( cmd, execOptions, function( err, stdo, stde ) { 62 | if ( !options.quiet ) { 63 | process.stderr.write( stde.toString() ); 64 | } 65 | if ( typeof callback === 'function' ) { 66 | callback( err, options.stdoutAsBuffer ? stdo : stdo.toString() ); 67 | } 68 | } ); 69 | }; 70 | 71 | 72 | var executeSvn = function( params, options, callback ) { 73 | options = options || {}; 74 | var cmd = ( options.svn || 'svn' ) + ' ' + params.join( " " ); 75 | execute( cmd, options, callback ); 76 | }; 77 | 78 | 79 | var executeSvnXml = function( params, options, callback ) { 80 | executeSvn( params.concat( [ '--xml' ] ), options, function( err, data ) { 81 | if ( !err ) { 82 | xmlToJson( data, function( err2, json ) { 83 | callback( err2, json ); 84 | } ); 85 | } else { 86 | callback( err, null ); 87 | } 88 | } ); 89 | }; 90 | 91 | 92 | var executeMucc = function( params, options, callback ) { 93 | options = options || {}; 94 | var cmd = ( options.svnmucc || 'svnmucc' ) + ' ' + params.join( " " ); 95 | execute( cmd, options, callback ); 96 | }; 97 | 98 | 99 | var execSvnVersion = function( params, options, callback ) { 100 | options = options || {}; 101 | var cmd = ( options.svnversion || 'svnversion' ) + ' ' + params.join( " " ); 102 | options.noStandardOptions = true; 103 | execute( cmd, options, callback ); 104 | }; 105 | 106 | 107 | var checkSvnVersion = function( options, reqVersion, callback ) { 108 | options = options || {}; 109 | options.noStandardOptions = true; 110 | var cmd = ( options.svn || 'svn' ) + ' --version --quiet'; 111 | execute( cmd, options, function( err, stdo ) { 112 | if ( err ) { 113 | return callback( err ); 114 | } 115 | stdo = stdo.trim(); 116 | callback( null, semver.satisfies( stdo, reqVersion ) ); 117 | } ); 118 | }; 119 | 120 | 121 | var addExtraOptions = function( validOptionsArray, options, addRevProp ) { 122 | if ( options ) { 123 | options.params = options.params || []; 124 | validOptionsArray.forEach( function( validOption ) { 125 | switch ( validOption ) { 126 | case 'force': 127 | if ( options.force ) { 128 | options.params.push('--force'); 129 | } 130 | break; 131 | case 'quiet': 132 | if ( options.quiet ) { 133 | options.params.push('--quiet'); 134 | } 135 | break; 136 | case 'revision': 137 | if ( options.revision ) { 138 | options.params.push('--revision', options.revision.toString()); 139 | if ( addRevProp ) { 140 | options.params.push( '--revprop' ); 141 | } 142 | } 143 | break; 144 | case 'depth': 145 | if ( options.depth ) { 146 | options.params.push('--depth', options.depth.toString()); 147 | } 148 | break; 149 | case 'ignoreExternals': 150 | if ( options.ignoreExternals ) { 151 | options.params.push('--ignore-externals'); 152 | } 153 | break; 154 | case 'msg': 155 | if ( options.msg ) { 156 | options.params.push('-m', '"' + options.msg + '"'); 157 | } 158 | break; 159 | case 'verbose': 160 | if ( options.verbose ) { 161 | options.params.push('--verbose'); 162 | } 163 | break; 164 | case 'showRevs': 165 | if ( options.showRevs ) { 166 | options.params.push('--show-revs', options.showRevs); 167 | } 168 | break; 169 | case 'limit': 170 | if ( options.limit ) { 171 | options.params.push('--limit', options.limit.toString()); 172 | } 173 | break; 174 | } 175 | } ); 176 | } 177 | return options; 178 | }; 179 | 180 | /** Exposes the commands for the command line svn tool. 181 | * @namespace commands 182 | */ 183 | exports.commands = {}; 184 | 185 | 186 | /** Checks out a repository to a working copy 187 | * @function checkout 188 | * @memberof commands 189 | * @param {string} url - Repository URL 190 | * @param {string} dir - Working copy dir 191 | * @param {object} [options] - Options object 192 | * @param {function} [callback] - Complete callback 193 | * @alias co 194 | */ 195 | var checkout = function( url, dir, options, callback ) { 196 | if ( typeof options === 'function' ) { 197 | callback = options; 198 | options = null; 199 | } 200 | options = options || {}; 201 | addExtraOptions( [ 'force', 'quiet', 'revision', 'depth', 'ignoreExternals' ], options ); 202 | 203 | var dirPath = dir; 204 | if(typeof options.cwd !== 'undefined') 205 | dirPath = path.resolve(options.cwd, dir); 206 | if ( !fs.existsSync( dirPath ) ) { 207 | fs.mkdirsSync( dirPath ); 208 | } 209 | executeSvn( [ 'checkout', url, dir ], options, callback ); 210 | }; 211 | exports.commands.checkout = checkout; 212 | exports.commands.co = checkout; 213 | 214 | /** Adds a file / folder to a working copy 215 | * @function add 216 | * @memberof commands 217 | * @param {Array|string} files - Add given files / folders 218 | * @param {object} [options] - Options object 219 | * @param {function} [callback] - Complete callback 220 | */ 221 | var add = function( files, options, callback ) { 222 | if ( !Array.isArray( files ) ) { 223 | files = [files]; 224 | } 225 | if ( typeof options === 'function' ) { 226 | callback = options; 227 | options = null; 228 | } 229 | options = options || {}; 230 | addExtraOptions( [ 'force', 'quiet', 'depth' ], options ); 231 | executeSvn( [ 'add' ].concat( files ), options, callback ); 232 | }; 233 | exports.commands.add = add; 234 | 235 | // TODO: blame 236 | 237 | /** Gets the content of a file from either a working copy or a URL. 238 | * @function cat 239 | * @memberof commands 240 | * @param {Array|string} targets - Array of URLs or working copy files to catalogue 241 | * @param {object} [options] - Options object 242 | * @param {function} [callback] - Complete callback 243 | */ 244 | var cat = function( targets, options, callback ) { 245 | if ( !Array.isArray( targets ) ) { 246 | targets = [targets]; 247 | } 248 | if ( typeof options === 'function' ) { 249 | callback = options; 250 | options = null; 251 | } 252 | options = options || {}; 253 | addExtraOptions( [ 'revision' ], options ); 254 | executeSvn( [ 'cat' ].concat( targets ), options, callback ); 255 | }; 256 | exports.commands.cat = cat; 257 | 258 | // TODO: changelist (cl) 259 | 260 | /** Performs an svn cleanup operation on the working copy 261 | * @function cleanup 262 | * @memberof commands 263 | * @param {string} wc - Working copy directory to clean 264 | * @param {object} [options] - Options object 265 | * @param {function} [callback] - Complete callback 266 | */ 267 | var cleanup = function( wc, options, callback ) { 268 | if ( typeof options === 'function' ) { 269 | callback = options; 270 | options = null; 271 | } 272 | options = options || {}; 273 | executeSvn( [ 'cleanup', wc ], options, callback ); 274 | }; 275 | exports.commands.cleanup = cleanup; 276 | 277 | /** Commits a working copy to a repository 278 | * @function commit 279 | * @memberof commands 280 | * @param {Array|string} files - Array of files / folders to commit 281 | * @param {object} [options] - Options object 282 | * @param {function} [callback] - Complete callback 283 | * @alias ci 284 | */ 285 | var commit = function( files, options, callback ) { 286 | if ( !Array.isArray( files ) ) { 287 | files = [files]; 288 | } 289 | if ( typeof options === 'function' ) { 290 | callback = options; 291 | options = null; 292 | } else if ( typeof options === 'string' ) { 293 | options = { msg: options }; 294 | } 295 | options = options || {}; 296 | addExtraOptions( [ 'quiet', 'depth', 'msg' ], options ); 297 | executeSvn( [ 'commit' ].concat( files ), options, callback ); 298 | }; 299 | exports.commands.commit = commit; 300 | exports.commands.ci = commit; 301 | 302 | /** Copies a file / folder within either a working copy or a URL 303 | * @function copy 304 | * @memberof commands 305 | * @param {Array|string} srcs - URLs / files to copy 306 | * @param {string} dst - destination 307 | * @param {object} [options] - Options object 308 | * @param {function} [callback] - Complete callback 309 | * @alias cp 310 | */ 311 | var copy = function( srcs, dst, options, callback ) { 312 | if ( !Array.isArray( srcs ) ) { 313 | srcs = [srcs]; 314 | } 315 | if ( typeof options === 'function' ) { 316 | callback = options; 317 | options = null; 318 | } else if ( typeof options === 'string' ) { 319 | options = { msg: options }; 320 | } 321 | options = options || {}; 322 | addExtraOptions( [ 'revision', 'quiet', 'depth', 'msg' ], options ); 323 | executeSvn( [ 'copy' ].concat( srcs ).concat( [ dst ] ), options, callback ); 324 | }; 325 | exports.commands.copy = copy; 326 | exports.commands.cp = copy; 327 | 328 | /** Deletes a file/folder from either a working copy or a URL 329 | * @function del 330 | * @memberof commands 331 | * @param {Array|string} srcs - Array of URLs / files to delete 332 | * @param {object} [options] - Options object 333 | * @param {function} [callback] - Complete callback 334 | * @alias remove 335 | * @alias rm 336 | */ 337 | var del = function( srcs, options, callback ) { 338 | if ( !Array.isArray( srcs ) ) { 339 | srcs = [srcs]; 340 | } 341 | if ( typeof options === 'function' ) { 342 | callback = options; 343 | options = null; 344 | } else if ( typeof options === 'string' ) { 345 | options = { msg: options }; 346 | } 347 | options = options || {}; 348 | addExtraOptions( [ 'quiet', 'force', 'msg' ], options ); 349 | executeSvn( [ 'del' ].concat( srcs ), options, callback ); 350 | }; 351 | exports.commands.del = del; 352 | exports.commands.remove = del; 353 | exports.commands.rm = del; 354 | 355 | // var diff = function( src, dest, options, callback ) { 356 | // if ( typeof options === 'function' ) { 357 | // callback = options; 358 | // options = null; 359 | // } 360 | // options = options || {}; 361 | // addExtraOptions( [ 'revision', 'depth', 'force' ], options ); 362 | // executeSvn( [ 'export', src, dst ], options, callback ); 363 | // }; 364 | // exports.commands.diff = diff; 365 | 366 | 367 | /** Exports a file from the repository to a local file 368 | * @function export 369 | * @memberof commands 370 | * @param {string} src - Source URL 371 | * @param {string} dst - Destination file 372 | * @param {object} [options] - Options object 373 | * @param {function} [callback] - Complete callback 374 | * @alias exp 375 | */ 376 | var exp = function( src, dst, options, callback ) { 377 | if ( typeof options === 'function' ) { 378 | callback = options; 379 | options = null; 380 | } 381 | options = options || {}; 382 | addExtraOptions( [ 'revision', 'quiet', 'force' ], options ); 383 | executeSvn( [ 'export', src, dst ], options, callback ); 384 | }; 385 | exports.commands.export = exp; 386 | exports.commands.exp = exp; 387 | 388 | /** Imports a file to the repository 389 | * @function import 390 | * @memberof commands 391 | * @param {string} src - Source file 392 | * @param {string} dst - Destination URL 393 | * @param {object} [options] - Options object 394 | * @param {function} [callback] - Complete callback 395 | * @alias imp 396 | */ 397 | var imp = function( src, dst, options, callback ) { 398 | if ( typeof options === 'function' ) { 399 | callback = options; 400 | options = null; 401 | } else if ( typeof options === 'string' ) { 402 | options = { msg: options }; 403 | } 404 | options = options || {}; 405 | addExtraOptions( [ 'depth', 'quiet', 'force', 'msg' ], options ); 406 | executeSvn( [ 'import', src, dst ], options, callback ); 407 | }; 408 | exports.commands.import = imp; 409 | exports.commands.imp = imp; 410 | 411 | /** Performs an svn info command on a given working copy file / URL 412 | * @function info 413 | * @memberof commands 414 | * @param {Array|string} targets - Target URLs / files to info 415 | * @param {object} [options] - Options object 416 | * @param {function} [callback] - Complete callback 417 | */ 418 | var info = function( targets, options, callback ) { 419 | if ( !Array.isArray( targets ) ) { 420 | targets = [targets]; 421 | } 422 | if ( typeof options === 'function' ) { 423 | callback = options; 424 | options = null; 425 | } 426 | options = options || {}; 427 | addExtraOptions( [ 'depth', 'revision' ], options ); 428 | executeSvnXml( [ 'info' ].concat( targets ), options, callback ); 429 | }; 430 | exports.commands.info = info; 431 | 432 | /** Lists the files within a directory, either working copy or URL 433 | * @function list 434 | * @memberof commands 435 | * @param {Array|string} targets - Target URLs / files to list 436 | * @param {object} [options] - Options object 437 | * @param {function} [callback] - Complete callback 438 | * @alias ls 439 | */ 440 | var list = function( targets, options, callback ) { 441 | if ( !Array.isArray( targets ) ) { 442 | targets = [targets]; 443 | } 444 | if ( typeof options === 'function' ) { 445 | callback = options; 446 | options = null; 447 | } 448 | options = options || {}; 449 | addExtraOptions( [ 'depth', 'revision' ], options ); 450 | executeSvnXml( [ 'list' ].concat( targets ), options, callback ); 451 | }; 452 | exports.commands.list = list; 453 | exports.commands.ls = list; 454 | 455 | /** Locks a file in a working copy / repository 456 | * @function lock 457 | * @memberof commands 458 | * @param {Array|string} targets - Target URLs / files to lock 459 | * @param {object} [options] - Options object 460 | * @param {function} [callback] - Complete callback 461 | */ 462 | var lock = function( targets, options, callback ) { 463 | if ( !Array.isArray( targets ) ) { 464 | targets = [targets]; 465 | } 466 | if ( typeof options === 'function' ) { 467 | callback = options; 468 | options = null; 469 | } else if ( typeof options === 'string' ) { 470 | options = { msg: options }; 471 | } 472 | options = options || {}; 473 | addExtraOptions( [ 'force', 'msg' ], options ); 474 | executeSvn( [ 'lock' ].concat( targets ), options, callback ); 475 | }; 476 | exports.commands.lock = lock; 477 | 478 | /** Gets the SVN message log and returns as a JSON object 479 | * @function log 480 | * @memberof commands 481 | * @param {Array|string} targets - Target URLs / files to get logs for 482 | * @param {object} [options] - Options object 483 | * @param {function} [callback] - Complete callback 484 | */ 485 | var log = function( targets, options, callback ) { 486 | if ( !Array.isArray( targets ) ) { 487 | targets = [targets]; 488 | } 489 | if ( typeof options === 'function' ) { 490 | callback = options; 491 | options = null; 492 | } 493 | options = options || {}; 494 | addExtraOptions( [ 'quiet', 'depth', 'revision', 'verbose','limit' ], options ); 495 | executeSvnXml( [ 'log' ].concat( targets ), options, callback ); 496 | }; 497 | exports.commands.log = log; 498 | 499 | 500 | /** Apply the differences between two sources to a working copy path. 501 | * @function merge 502 | * @memberof commands 503 | * @param {Array|string} targets - Target URLs 504 | * @param {object} [options] - Options object 505 | * @param {function} [callback] - Complete callback 506 | */ 507 | var merge = function( targets, options, callback ) { 508 | if ( !Array.isArray( targets ) ) { 509 | targets = [targets]; 510 | } 511 | if ( typeof options === 'function' ) { 512 | callback = options; 513 | options = null; 514 | } 515 | options = options || {}; 516 | addExtraOptions( [ 'force', 'quiet', 'revision', 'depth' ], options ); 517 | executeSvn( [ 'merge' ].concat( targets ), options, callback ); 518 | }; 519 | exports.commands.merge = merge; 520 | 521 | /** Query information related to merges (or potential merges) between SOURCE and TARGET. 522 | * @function mergeinfo 523 | * @memberof commands 524 | * @param {string} source - SOURCE URL 525 | * @param {string} target - TARGET URL 526 | * @param {object} [options] - Options object 527 | * @param {function} [callback] - Complete callback 528 | */ 529 | var mergeinfo = function( source, target, options, callback ) { 530 | if ( typeof options === 'function' ) { 531 | callback = options; 532 | options = null; 533 | } 534 | options = options || {}; 535 | addExtraOptions( [ 'quiet', 'msg', 'verbose', 'log', 'showRevs' ], options ); 536 | executeSvn( [ 'mergeinfo' ].concat( source ).concat( target ), options, callback ); 537 | }; 538 | exports.commands.mergeinfo = mergeinfo; 539 | 540 | /** Creates a directory in the working copy or repository 541 | * @function mkdir 542 | * @memberof commands 543 | * @param {Array|string} targets - Target URLs / folders to create 544 | * @param {object} [options] - Options object 545 | * @param {function} [callback] - Complete callback 546 | */ 547 | var mkdir = function( targets, options, callback ) { 548 | if ( !Array.isArray( targets ) ) { 549 | targets = [targets]; 550 | } 551 | if ( typeof options === 'function' ) { 552 | callback = options; 553 | options = null; 554 | } else if ( typeof options === 'string' ) { 555 | options = { msg: options }; 556 | } 557 | options = options || {}; 558 | addExtraOptions( [ 'quiet', 'msg' ], options ); 559 | executeSvn( [ 'mkdir' ].concat( targets ), options, callback ); 560 | }; 561 | exports.commands.mkdir = mkdir; 562 | 563 | /** Moves a file / folder in a working copy or URL 564 | * @function move 565 | * @memberof commands 566 | * @param {Array|string} srcs - Target URLs / files to move 567 | * @param {string} dst - Destination URL / file 568 | * @param {object} [options] - Options object 569 | * @param {function} [callback] - Complete callback 570 | * @alias mv 571 | * @alias rename 572 | * @alias ren 573 | */ 574 | var move = function( srcs, dst, options, callback ) { 575 | if ( !Array.isArray( srcs ) ) { 576 | srcs = [srcs]; 577 | } 578 | if ( typeof options === 'function' ) { 579 | callback = options; 580 | options = null; 581 | } else if ( typeof options === 'string' ) { 582 | options = { msg: options }; 583 | } 584 | options = options || {}; 585 | addExtraOptions( [ 'quiet', 'force', 'msg' ], options ); 586 | executeSvn( [ 'move' ].concat( srcs ).concat( [ dst ] ), options, callback ); 587 | }; 588 | exports.commands.move = move; 589 | exports.commands.mv = move; 590 | exports.commands.rename = move; 591 | exports.commands.ren = move; 592 | 593 | // var patch = function( patchFile, wc, options, callback ) { 594 | // if ( typeof options === 'function' ) { 595 | // callback = options; 596 | // options = null; 597 | // } 598 | // options = options || {}; 599 | // executeSvn( [ 'patch', patchFile, wc ], options, callback ); 600 | // }; 601 | // exports.commands.patch = patch; 602 | 603 | /** Deletes an svn property from a working copy / repository 604 | * @function propdel 605 | * @memberof commands 606 | * @param {string} propName - Property name 607 | * @param {string} target - Target file / folder or URL 608 | * @param {object} [options] - Options object 609 | * @param {function} [callback] - Complete callback 610 | * @alias pdel 611 | * @alias pd 612 | */ 613 | var propdel = function( propName, target, options, callback ) { 614 | if ( typeof options === 'function' ) { 615 | callback = options; 616 | options = null; 617 | } 618 | options = options || {}; 619 | addExtraOptions( [ 'quiet', 'depth' ], options, true ); 620 | executeSvn( [ 'propdel', propName, target ], options, callback ); 621 | }; 622 | exports.commands.propdel = propdel; 623 | exports.commands.pdel = propdel; 624 | exports.commands.pd = propdel; 625 | 626 | // propedit (pedit, pe) - not supported 627 | 628 | /** Gets an svn property from a working copy / repository 629 | * @function propget 630 | * @memberof commands 631 | * @param {string} propName - Property name 632 | * @param {Array|string} targets - Target file / folder or URL 633 | * @param {object} [options] - Options object 634 | * @param {function} [callback] - Complete callback 635 | * @alias pget 636 | * @alias pg 637 | */ 638 | var propget = function( propName, targets, options, callback ) { 639 | if ( typeof options === 'function' ) { 640 | callback = options; 641 | options = null; 642 | } 643 | if ( !Array.isArray( targets ) ) { 644 | targets = [targets]; 645 | } 646 | options = options || {}; 647 | addExtraOptions( [ 'depth', 'revision' ], options, true ); 648 | executeSvnXml( [ 'propget', propName ].concat( targets ), options, callback ); 649 | }; 650 | exports.commands.propget = propget; 651 | exports.commands.pget = propget; 652 | exports.commands.pg = propget; 653 | 654 | /** Lists svn properties from a working copy / repository 655 | * @function proplist 656 | * @memberof commands 657 | * @param {Array|string} targets - Target file / folder or URL 658 | * @param {object} [options] - Options object 659 | * @param {function} [callback] - Complete callback 660 | * @alias plist 661 | * @alias pl 662 | */ 663 | var proplist = function( targets, options, callback ) { 664 | if ( typeof options === 'function' ) { 665 | callback = options; 666 | options = null; 667 | } 668 | if ( !Array.isArray( targets ) ) { 669 | targets = [targets]; 670 | } 671 | options = options || {}; 672 | addExtraOptions( [ 'quiet', 'depth', 'revision' ], options, true ); 673 | executeSvnXml( [ 'proplist' ].concat( targets ), options, callback ); 674 | }; 675 | exports.commands.proplist = proplist; 676 | exports.commands.plist = proplist; 677 | exports.commands.pl = proplist; 678 | 679 | /** Sets an svn property from a working copy / repository 680 | * @function propset 681 | * @memberof commands 682 | * @param {string} propName - Property name 683 | * @param {string} propVal - Property value 684 | * @param {string} wc - Target file / folder or URL 685 | * @param {object} [options] - Options object 686 | * @param {function} [callback] - Complete callback 687 | * @alias pset 688 | * @alias ps 689 | */ 690 | var propset = function( propName, propVal, wc, options, callback ) { 691 | if ( typeof options === 'function' ) { 692 | callback = options; 693 | options = null; 694 | } 695 | options = options || {}; 696 | addExtraOptions( [ 'quiet', 'depth', 'revision', 'force' ], options, true ); 697 | executeSvn( [ 'propset', propName, propVal, wc ], options, callback ); 698 | }; 699 | exports.commands.propset = propset; 700 | exports.commands.pset = propset; 701 | exports.commands.ps = propset; 702 | 703 | /** Relocates an svn working copy 704 | * @function relocate 705 | * @memberof commands 706 | * @param {string} url - Relocation URL 707 | * @param {string} wc - Working copy to relocate 708 | * @param {object} [options] - Options object 709 | * @param {function} [callback] - Complete callback 710 | */ 711 | var relocate = function( url, wc, options, callback ) { 712 | if ( typeof options === 'function' ) { 713 | callback = options; 714 | options = null; 715 | } 716 | options = options || {}; 717 | executeSvn( [ 'relocate', url, wc ], options, callback ); 718 | }; 719 | exports.commands.relocate = relocate; 720 | 721 | // resolve/resolved - probably doesn't make sense to automate 722 | 723 | /** Reverts files / folders in a working copy to their uncommited state 724 | * @function revert 725 | * @memberof commands 726 | * @param {string} wc - Working copy target 727 | * @param {object} [options] - Options object 728 | * @param {function} [callback] - Complete callback 729 | */ 730 | var revert = function( wc, options, callback ) { 731 | if ( typeof options === 'function' ) { 732 | callback = options; 733 | options = null; 734 | } 735 | options = options || {}; 736 | addExtraOptions( [ 'quiet', 'depth' ], options ); 737 | if ( !Array.isArray( wc ) ) { 738 | wc = [wc]; 739 | } 740 | executeSvn( [ 'revert' ].concat( wc ), options, callback ); 741 | }; 742 | exports.commands.revert = revert; 743 | 744 | /** Performs an svn status command on a working copy 745 | * @function status 746 | * @memberof commands 747 | * @param {string} wc - Working copy target 748 | * @param {object} [options] - Options object 749 | * @param {function} [callback] - Complete callback 750 | * @alias stat 751 | * @alias st 752 | */ 753 | var status = function( wc, options, callback ) { 754 | if ( typeof options === 'function' ) { 755 | callback = options; 756 | options = null; 757 | } 758 | options = options || {}; 759 | addExtraOptions( [ 'quiet', 'depth' ], options ); 760 | executeSvnXml( [ 'status', wc ], options, callback ); 761 | }; 762 | exports.commands.status = status; 763 | exports.commands.stat = status; 764 | exports.commands.st = status; 765 | 766 | /** Switches to a given branch / tag for a working copy 767 | * @function switch 768 | * @memberof commands 769 | * @param {string} url - Switch URL 770 | * @param {string} wc - Working copy target 771 | * @param {object} [options] - Options object 772 | * @param {function} [callback] - Complete callback 773 | */ 774 | var switchf = function( url, wc, options, callback ) { 775 | if ( typeof options === 'function' ) { 776 | callback = options; 777 | options = null; 778 | } 779 | options = options || {}; 780 | addExtraOptions( [ 'quiet', 'depth', 'revision', 'force' ], options ); 781 | executeSvn( [ 'switch', url, wc ], options, callback ); 782 | }; 783 | exports.commands.switch = switchf; 784 | 785 | /** Unlocks a previously locked svn file from a working copy / repository 786 | * @function unlock 787 | * @memberof commands 788 | * @param {Array|string} targets - Working copy / URL targets 789 | * @param {object} [options] - Options object 790 | * @param {function} [callback] - Complete callback 791 | */ 792 | var unlock = function( targets, options, callback ) { 793 | if ( typeof options === 'function' ) { 794 | callback = options; 795 | options = null; 796 | } 797 | if ( !Array.isArray( targets ) ) { 798 | targets = [targets]; 799 | } 800 | options = options || {}; 801 | addExtraOptions( [ 'force' ], options ); 802 | executeSvn( [ 'unlock' ].concat( targets ), options, callback ); 803 | }; 804 | exports.commands.unlock = unlock; 805 | 806 | /** Updates an svn working copy 807 | * @function update 808 | * @memberof commands 809 | * @param {Array|string} wcs - Working copy targets 810 | * @param {object} [options] - Options object 811 | * @param {function} [callback] - Complete callback 812 | * @alias up 813 | */ 814 | var update = function( wcs, options, callback ) { 815 | if ( typeof options === 'function' ) { 816 | callback = options; 817 | options = null; 818 | } 819 | if ( !Array.isArray( wcs ) ) { 820 | wcs = [wcs]; 821 | } 822 | options = options || {}; 823 | addExtraOptions( [ 'force', 'quiet', 'revision', 'depth', 'ignoreExternals' ], options ); 824 | executeSvn( [ 'update' ].concat( wcs ), options, callback ); 825 | }; 826 | exports.commands.update = update; 827 | exports.commands.up = update; 828 | 829 | /** Upgrades a given svn working copy (requires v1.7 of svn client) 830 | * @function upgrade 831 | * @memberof commands 832 | * @param {Array|string} wcs - Working copy targets 833 | * @param {object} [options] - Options object 834 | * @param {function} [callback] - Complete callback 835 | */ 836 | var upgrade = function( wcs, options, callback ) { 837 | // upgrade only works for versions of svn >= 1.7 838 | if ( typeof options === 'function' ) { 839 | callback = options; 840 | options = null; 841 | } 842 | if ( !Array.isArray( wcs ) ) { 843 | wcs = [wcs]; 844 | } 845 | options = options || {}; 846 | addExtraOptions( [ 'quiet' ], options ); 847 | 848 | checkSvnVersion( options, ">=1.7.0", function( err, isValid ) { 849 | if ( err ) { 850 | return callback( err ); 851 | } 852 | if ( isValid ) { 853 | executeSvn( [ 'upgrade' ].concat( wcs ), options, callback ); 854 | } else { 855 | callback(); 856 | } 857 | } ); 858 | }; 859 | exports.commands.upgrade = upgrade; 860 | 861 | 862 | // svnmucc 863 | /* 864 | commandArray can contain array of the following 865 | cp REV SRC-URL DST-URL : copy SRC-URL@REV to DST-URL 866 | mkdir URL : create new directory URL 867 | mv SRC-URL DST-URL : move SRC-URL to DST-URL 868 | rm URL : delete URL 869 | put SRC-FILE URL : add or modify file URL with contents copied from 870 | SRC-FILE (use "-" to read from standard input) 871 | propset NAME VALUE URL : set property NAME on URL to VALUE 872 | propsetf NAME FILE URL : set property NAME on URL to value read from FILE 873 | propdel NAME URL : delete property NAME from URL 874 | */ 875 | 876 | /** Executes svnmucc command, for multiple commands 877 | * @see http://svnbook.red-bean.com/en/1.8/svn.ref.svnmucc.re.html 878 | * @function mucc 879 | * @memberof commands 880 | * @param {Array} commandArray - Array of command strings, see above link for options 881 | * @param {string} commitMessage - Commit message to use 882 | * @param {object} [options] - Options object 883 | * @param {function} [callback] - Complete callback 884 | */ 885 | var mucc = function( commandArray, commitMessage, options, callback ) { 886 | if ( typeof commitMessage !== 'string' ) { 887 | throw new Error( "svnUltimate.command.mucc - commitMessage must be a string" ); 888 | } 889 | if ( typeof options === 'function' ) { 890 | callback = options; 891 | options = null; 892 | } 893 | options = options || {}; 894 | if ( !Array.isArray( commandArray ) ) { 895 | commandArray = [commandArray]; 896 | } 897 | executeMucc( [ '-m "' + commitMessage + '"' ].concat( commandArray ), options, function( err, stdo ) { 898 | // parse the output to find the revision that has just been commited 899 | var result = null; 900 | stdo = stdo.toString().trim(); 901 | try { 902 | var matches = stdo.match( /^r(\d+) committed by (.+?) at (.+)$/ ); 903 | if ( matches ) { // matches should be revision, name, then iso date 904 | result = { 905 | revision: parseInt( matches[1], 10 ), 906 | user: matches[2], 907 | isodate: matches[3] 908 | }; 909 | } 910 | } 911 | catch ( err2 ) { 912 | } 913 | callback( err, result ); 914 | } ); 915 | }; 916 | exports.commands.mucc = mucc; 917 | 918 | 919 | // Utilities 920 | 921 | /** Exposes some custom utility methods 922 | * @namespace util 923 | */ 924 | exports.util = {}; 925 | // 'lastChangeRevision' option returns the last commit revision, instead of the working copy revision 926 | 927 | /** Gets head revision of a given URL 928 | * @function getRevision 929 | * @memberof util 930 | * @param {string} target - Target URL 931 | * @param {object} [options] - Options object 932 | * @param {function} [callback] - Complete callback 933 | */ 934 | var getRevision = function( target, options, callback ) { 935 | if ( typeof options === "function" ) { 936 | callback = options; 937 | options = null; 938 | } 939 | options = options || {}; 940 | info( target, options, function( err, data ) { 941 | var rev; 942 | if ( !err ) { 943 | var revString; 944 | if ( options.lastChangeRevision ) { 945 | if ( data && data.entry && data.entry.commit && data.entry.commit.$ && data.entry.commit.$.revision ) { 946 | revString = data.entry.commit.$.revision; 947 | } 948 | } else { 949 | if ( data && data.entry && data.entry.$ && data.entry.$.revision ) { 950 | revString = data.entry.$.revision; 951 | } 952 | } 953 | if ( revString !== undefined ) { 954 | try { 955 | rev = parseInt( revString, 10 ); 956 | } 957 | catch ( err3 ) { 958 | err = 'Invalid revision value [' + revString + ']'; 959 | } 960 | } else { 961 | err = 'Could not parse info result to get revision [' + JSON.stringify( data ) + ']'; 962 | } 963 | } 964 | callback( err, rev ); 965 | } ); 966 | }; 967 | exports.util.getRevision = getRevision; 968 | 969 | 970 | /** Gets the revision of a working copy. 971 | * @function getWorkingCopyRevision 972 | * @memberof util 973 | * @param {string} wcDir - Working copy folder 974 | * @param {object} [options] - Options object 975 | * @param {function} [callback] - Complete callback 976 | */ 977 | var getWorkingCopyRevision = function( wcDir, options, callback ) { 978 | if ( typeof options === "function" ) { 979 | callback = options; 980 | options = null; 981 | } 982 | options = options || {}; 983 | if ( !Array.isArray( wcDir ) ) { 984 | wcDir = [wcDir]; 985 | } 986 | var args = [ '-n' ]; 987 | if ( options.lastChangeRevision ) { 988 | args.push( '-c' ); 989 | } 990 | execSvnVersion( wcDir.concat( args ), options, function( err, data ) { 991 | var result; 992 | if ( !err ) { 993 | var match = data.match( /(\d+):?(\d*)(\w*)/ ); 994 | if ( match ) { 995 | result = {}; 996 | result.low = parseInt( match[1] ); 997 | if ( match[2].length > 0 ) { 998 | result.high = parseInt( match[2] ); 999 | } else { 1000 | result.high = result.low; 1001 | } 1002 | result.flags = match[3]; 1003 | if ( result.flags.length > 0 ) { 1004 | result.modified = result.flags.indexOf( 'M' ) >= 0; 1005 | result.partial = result.flags.indexOf( 'P' ) >= 0; 1006 | result.switched = result.flags.indexOf( 'S' ) >= 0; 1007 | } 1008 | } else { 1009 | err = data; 1010 | } 1011 | } 1012 | callback( err, result ); 1013 | } ); 1014 | }; 1015 | exports.util.getWorkingCopyRevision = getWorkingCopyRevision; 1016 | 1017 | 1018 | /** Parse a url for an SVN project repository and breaks it apart 1019 | * @function parseUrl 1020 | * @memberof util 1021 | * @param {string} url - URL to parse 1022 | * @returns {object} 1023 | */ 1024 | var parseUrl = function( url ) { 1025 | var trunkMatch = url.match( /(.*)\/(trunk|branches|tags)\/*(.*)\/*(.*)$/i ); 1026 | if ( trunkMatch ) { 1027 | var rootUrl = trunkMatch[1]; 1028 | var projectName = rootUrl.match( /\/([^\/]+)$/ )[1]; 1029 | return { 1030 | rootUrl: rootUrl, 1031 | projectName: projectName, 1032 | type: trunkMatch[2], 1033 | typeName: trunkMatch[3], 1034 | trunkUrl: trunkMatch[1] + "/trunk", 1035 | tagsUrl: trunkMatch[1] + "/tags", 1036 | branchesUrl: trunkMatch[1] + "/branches" 1037 | }; 1038 | } 1039 | throw new Error( "parseUrl: Url does not look like an SVN repository" ); 1040 | }; 1041 | exports.util.parseUrl = parseUrl; 1042 | 1043 | /** Gets all available tags for the given svn URL 1044 | * @function getTags 1045 | * @memberof util 1046 | * @param {string} url - Project URL to get tags for 1047 | * @param {object} [options] - Options object 1048 | * @param {function} [callback] - Complete callback 1049 | */ 1050 | var getTags = function( url, options, callback ) { 1051 | if ( typeof options === "function" ) { 1052 | callback = options; 1053 | options = null; 1054 | } 1055 | options = options || {}; 1056 | var tagsUrl = parseUrl( url ).tagsUrl; 1057 | list( tagsUrl, options, function( err, data ) { 1058 | var result = []; 1059 | if ( !err && data && data.list && data.list.entry ) { 1060 | if ( Array.isArray( data.list.entry ) ) { 1061 | result = data.list.entry.filter( function( entry ) { 1062 | return entry && entry.$ && entry.$.kind === "dir"; 1063 | } ); 1064 | } else { 1065 | if ( data.list.entry.$ && data.list.entry.$.kind === "dir" ) { 1066 | result = [ data.list.entry ]; 1067 | } 1068 | } 1069 | } 1070 | callback( err, result ); 1071 | } ); 1072 | }; 1073 | exports.util.getTags = getTags; 1074 | 1075 | 1076 | /** Uses node's semver package to work out the latest tag value 1077 | * @function getLatestTag 1078 | * @memberof util 1079 | * @param {string} url - Project URL to get latest tag for 1080 | * @param {object} options - Options object 1081 | * @param {function} [callback] - Complete callback 1082 | */ 1083 | var getLatestTag = function( url, options, callback ) { 1084 | if ( typeof options === "function" ) { 1085 | callback = options; 1086 | options = null; 1087 | } 1088 | options = options || {}; 1089 | getTags( url, options, function( err, tagArray ) { 1090 | var latest; 1091 | if ( !err && Array.isArray( tagArray ) && tagArray.length > 0 ) { 1092 | tagArray.sort( function( a, b ) { 1093 | try { 1094 | return semver.rcompare( a.name, b.name ); 1095 | } 1096 | catch ( err2 ) { 1097 | return -1; 1098 | } 1099 | } ); 1100 | latest = tagArray[0]; 1101 | } 1102 | callback( err, latest ); 1103 | } ); 1104 | }; 1105 | exports.util.getLatestTag = getLatestTag; 1106 | 1107 | /** Gets all available branches for the given svn URL 1108 | * @function getBranches 1109 | * @memberof util 1110 | * @param {string} url - Project URL to get branches for 1111 | * @param {object} [options] - Options object 1112 | * @param {function} [callback] - Complete callback 1113 | */ 1114 | var getBranches = function( url, options, callback ) { 1115 | if ( typeof options === "function" ) { 1116 | callback = options; 1117 | options = null; 1118 | } 1119 | options = options || {}; 1120 | var branchesUrl = parseUrl( url ).branchesUrl; 1121 | list( branchesUrl, options, function( err, data ) { 1122 | var result = []; 1123 | if ( !err && data && data.list && data.list.entry ) { 1124 | if ( Array.isArray( data.list.entry ) ) { 1125 | result = data.list.entry.filter( function( entry ) { 1126 | return entry && entry.$ && entry.$.kind === "dir"; 1127 | } ); 1128 | } else { 1129 | if ( data.list.entry.$ && data.list.entry.$.kind === "dir" ) { 1130 | result = [ data.list.entry ]; 1131 | } 1132 | } 1133 | } 1134 | callback( err, result ); 1135 | } ); 1136 | }; 1137 | exports.util.getBranches = getBranches; 1138 | 1139 | 1140 | /** Helper object for using SVNMUCC 1141 | */ 1142 | var MuccHelper = function( options ) { 1143 | this._options = options || {}; 1144 | this._commands = []; 1145 | this._options.tempFolder = this._options.tempFolder || path.join( os.tmpdir(), 'mucc_' + uuid.v4() ); 1146 | }; 1147 | 1148 | 1149 | MuccHelper.prototype._getTempFilename = function() { 1150 | fs.ensureDirSync( this._options.tempFolder ); 1151 | for ( var count=0; true; ++count ) { 1152 | var newPath = path.join( this._options.tempFolder, uuid.v4() + ".temp" ); 1153 | if ( !fs.existsSync( newPath ) ) { 1154 | return newPath; 1155 | } 1156 | } 1157 | throw new Error( "Could not find temporary filename" ); // never reached 1158 | }; 1159 | 1160 | 1161 | MuccHelper.prototype.queueLength = function() { 1162 | return this._commands.length; 1163 | }; 1164 | 1165 | 1166 | MuccHelper.prototype._reset = function( callback ) { 1167 | this._commands.length = 0; 1168 | if ( fs.existsSync( this._options.tempFolder ) ) { 1169 | fs.remove( this._options.tempFolder, function() { callback(); } ); 1170 | } else { 1171 | callback(); 1172 | } 1173 | }; 1174 | 1175 | 1176 | MuccHelper.prototype.cp = function( src, dst, options ) { 1177 | options = options || {}; 1178 | var str = 'cp'; 1179 | if ( isFinite( options.revision ) ) { 1180 | str += '\n' + options.revision; 1181 | } else { 1182 | str += '\nHEAD'; 1183 | } 1184 | str += '\n' + src; 1185 | str += '\n' + dst; 1186 | this._commands.push( str ); 1187 | }; 1188 | 1189 | 1190 | MuccHelper.prototype.putFile = function( filename, dst ) { 1191 | this._commands.push( 'put\n' + filename + '\n' + dst ); 1192 | }; 1193 | 1194 | 1195 | MuccHelper.prototype.put = function( filedata, dst ) { 1196 | var file = this._getTempFilename(); 1197 | fs.writeFileSync( file, filedata ); 1198 | this._commands.push( 'put\n' + file + '\n' + dst ); 1199 | }; 1200 | 1201 | 1202 | MuccHelper.prototype.mkdir = function( dst ) { 1203 | this._commands.push( 'mkdir\n' + dst ); 1204 | }; 1205 | 1206 | 1207 | MuccHelper.prototype.rm = function( dst ) { 1208 | this._commands.push( 'rm\n' + dst ); 1209 | }; 1210 | 1211 | 1212 | MuccHelper.prototype.commit = function( options, callback ) { 1213 | if ( typeof options === 'function' ) { 1214 | callback = options; 1215 | options = {}; 1216 | } 1217 | var that = this; 1218 | options = options || {}; 1219 | if ( this._commands.length > 0 ) { 1220 | // write arguments to file and pass to mucc via a temporary file to get around 1221 | // windows command line limitations 1222 | var argsFile = this._getTempFilename(); 1223 | fs.writeFileSync( argsFile, this._commands.join( "\n" ) ); 1224 | var params = ''; 1225 | if ( options.rootUrl && options.rootUrl.length > 0 ) { 1226 | params = '--root-url "' + options.rootUrl + '"'; 1227 | } 1228 | mucc( 1229 | params + ' --extra-args "' + argsFile + '"', 1230 | options.msg || 'SVNMUCC commit', 1231 | function( err, result ) { 1232 | that._reset( function( err2 ) { 1233 | callback( err || err2, result ); 1234 | } ); 1235 | } ); 1236 | } else { 1237 | callback( null, null ); 1238 | } 1239 | }; 1240 | 1241 | 1242 | 1243 | exports.util.MuccHelper = MuccHelper; 1244 | 1245 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-svn-ultimate", 3 | "version": "1.1.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "balanced-match": { 8 | "version": "1.0.0", 9 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 10 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 11 | "dev": true 12 | }, 13 | "brace-expansion": { 14 | "version": "1.1.11", 15 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 16 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 17 | "dev": true, 18 | "requires": { 19 | "balanced-match": "^1.0.0", 20 | "concat-map": "0.0.1" 21 | } 22 | }, 23 | "browser-stdout": { 24 | "version": "1.3.1", 25 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", 26 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", 27 | "dev": true 28 | }, 29 | "commander": { 30 | "version": "2.15.1", 31 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", 32 | "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", 33 | "dev": true 34 | }, 35 | "concat-map": { 36 | "version": "0.0.1", 37 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 38 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 39 | "dev": true 40 | }, 41 | "debug": { 42 | "version": "3.1.0", 43 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 44 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 45 | "dev": true, 46 | "requires": { 47 | "ms": "2.0.0" 48 | } 49 | }, 50 | "diff": { 51 | "version": "3.5.0", 52 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", 53 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", 54 | "dev": true 55 | }, 56 | "escape-string-regexp": { 57 | "version": "1.0.5", 58 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 59 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 60 | "dev": true 61 | }, 62 | "fs-extra": { 63 | "version": "1.0.0", 64 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", 65 | "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", 66 | "requires": { 67 | "graceful-fs": "^4.1.2", 68 | "jsonfile": "^2.1.0", 69 | "klaw": "^1.0.0" 70 | } 71 | }, 72 | "fs.realpath": { 73 | "version": "1.0.0", 74 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 75 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 76 | "dev": true 77 | }, 78 | "glob": { 79 | "version": "7.1.2", 80 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 81 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 82 | "dev": true, 83 | "requires": { 84 | "fs.realpath": "^1.0.0", 85 | "inflight": "^1.0.4", 86 | "inherits": "2", 87 | "minimatch": "^3.0.4", 88 | "once": "^1.3.0", 89 | "path-is-absolute": "^1.0.0" 90 | } 91 | }, 92 | "graceful-fs": { 93 | "version": "4.1.15", 94 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", 95 | "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" 96 | }, 97 | "growl": { 98 | "version": "1.10.5", 99 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", 100 | "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", 101 | "dev": true 102 | }, 103 | "has-flag": { 104 | "version": "3.0.0", 105 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 106 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 107 | "dev": true 108 | }, 109 | "he": { 110 | "version": "1.1.1", 111 | "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", 112 | "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", 113 | "dev": true 114 | }, 115 | "inflight": { 116 | "version": "1.0.6", 117 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 118 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 119 | "dev": true, 120 | "requires": { 121 | "once": "^1.3.0", 122 | "wrappy": "1" 123 | } 124 | }, 125 | "inherits": { 126 | "version": "2.0.3", 127 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 128 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 129 | "dev": true 130 | }, 131 | "jsonfile": { 132 | "version": "2.4.0", 133 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", 134 | "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", 135 | "requires": { 136 | "graceful-fs": "^4.1.6" 137 | } 138 | }, 139 | "klaw": { 140 | "version": "1.3.1", 141 | "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", 142 | "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", 143 | "requires": { 144 | "graceful-fs": "^4.1.9" 145 | } 146 | }, 147 | "minimatch": { 148 | "version": "3.0.4", 149 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 150 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 151 | "dev": true, 152 | "requires": { 153 | "brace-expansion": "^1.1.7" 154 | } 155 | }, 156 | "minimist": { 157 | "version": "0.0.8", 158 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 159 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 160 | "dev": true 161 | }, 162 | "mkdirp": { 163 | "version": "0.5.1", 164 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 165 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 166 | "dev": true, 167 | "requires": { 168 | "minimist": "0.0.8" 169 | } 170 | }, 171 | "mocha": { 172 | "version": "5.2.0", 173 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", 174 | "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", 175 | "dev": true, 176 | "requires": { 177 | "browser-stdout": "1.3.1", 178 | "commander": "2.15.1", 179 | "debug": "3.1.0", 180 | "diff": "3.5.0", 181 | "escape-string-regexp": "1.0.5", 182 | "glob": "7.1.2", 183 | "growl": "1.10.5", 184 | "he": "1.1.1", 185 | "minimatch": "3.0.4", 186 | "mkdirp": "0.5.1", 187 | "supports-color": "5.4.0" 188 | } 189 | }, 190 | "ms": { 191 | "version": "2.0.0", 192 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 193 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 194 | "dev": true 195 | }, 196 | "once": { 197 | "version": "1.4.0", 198 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 199 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 200 | "dev": true, 201 | "requires": { 202 | "wrappy": "1" 203 | } 204 | }, 205 | "path-is-absolute": { 206 | "version": "1.0.1", 207 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 208 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 209 | "dev": true 210 | }, 211 | "sax": { 212 | "version": "1.2.4", 213 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", 214 | "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" 215 | }, 216 | "semver": { 217 | "version": "5.6.0", 218 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", 219 | "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" 220 | }, 221 | "should": { 222 | "version": "11.2.1", 223 | "resolved": "https://registry.npmjs.org/should/-/should-11.2.1.tgz", 224 | "integrity": "sha1-kPVRRVUtAc/CAGZuToGKHJZw7aI=", 225 | "dev": true, 226 | "requires": { 227 | "should-equal": "^1.0.0", 228 | "should-format": "^3.0.2", 229 | "should-type": "^1.4.0", 230 | "should-type-adaptors": "^1.0.1", 231 | "should-util": "^1.0.0" 232 | } 233 | }, 234 | "should-equal": { 235 | "version": "1.0.1", 236 | "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-1.0.1.tgz", 237 | "integrity": "sha1-C26VFvJgGp+wuy3MNpr6HH4gCvc=", 238 | "dev": true, 239 | "requires": { 240 | "should-type": "^1.0.0" 241 | } 242 | }, 243 | "should-format": { 244 | "version": "3.0.3", 245 | "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", 246 | "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=", 247 | "dev": true, 248 | "requires": { 249 | "should-type": "^1.3.0", 250 | "should-type-adaptors": "^1.0.1" 251 | } 252 | }, 253 | "should-type": { 254 | "version": "1.4.0", 255 | "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", 256 | "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=", 257 | "dev": true 258 | }, 259 | "should-type-adaptors": { 260 | "version": "1.1.0", 261 | "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", 262 | "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", 263 | "dev": true, 264 | "requires": { 265 | "should-type": "^1.3.0", 266 | "should-util": "^1.0.0" 267 | } 268 | }, 269 | "should-util": { 270 | "version": "1.0.0", 271 | "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.0.tgz", 272 | "integrity": "sha1-yYzaN0qmsZDfi6h8mInCtNtiAGM=", 273 | "dev": true 274 | }, 275 | "supports-color": { 276 | "version": "5.4.0", 277 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", 278 | "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", 279 | "dev": true, 280 | "requires": { 281 | "has-flag": "^3.0.0" 282 | } 283 | }, 284 | "uuid": { 285 | "version": "3.3.2", 286 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", 287 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" 288 | }, 289 | "wrappy": { 290 | "version": "1.0.2", 291 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 292 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 293 | "dev": true 294 | }, 295 | "xml2js": { 296 | "version": "0.4.19", 297 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", 298 | "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", 299 | "requires": { 300 | "sax": ">=0.6.0", 301 | "xmlbuilder": "~9.0.1" 302 | } 303 | }, 304 | "xmlbuilder": { 305 | "version": "9.0.7", 306 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", 307 | "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" 308 | } 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-svn-ultimate", 3 | "description": "The ultimate SVN wrapper for node. Contains all the basic methods checkout, update, info, etc, and includes svnmucc support.", 4 | "version": "1.2.1", 5 | "homepage": "https://github.com/peteward44/node-svn-ultimate", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/peteward44/node-svn-ultimate.git" 9 | }, 10 | "author": "Pete Ward ", 11 | "main": "./index.js", 12 | "keywords": [ 13 | "svn" 14 | ], 15 | "ignore": [ 16 | "test", 17 | ".travis.yml", 18 | ".gitignore" 19 | ], 20 | "dependencies": { 21 | "fs-extra": "^1.0.0", 22 | "semver": "^5.3.0", 23 | "uuid": "^3.0.0", 24 | "xml2js": "^0.4.17" 25 | }, 26 | "devDependencies": { 27 | "mocha": "^5.2.0", 28 | "should": "^11.1.1" 29 | }, 30 | "engines": { 31 | "node": ">= 0.10.0" 32 | }, 33 | "licenses": [ 34 | { 35 | "type": "MIT", 36 | "url": "https://github.com/peteward44/gulp-artifactory-upload/blob/master/LICENSE" 37 | } 38 | ], 39 | "readmeFilename": "README.md", 40 | "bugs": { 41 | "url": "https://github.com/peteward44/node-svn-ultimate/issues" 42 | }, 43 | "scripts": { 44 | "test": "mocha", 45 | "docs": "jsdoc2md ./index.js > api.md" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/commands.js: -------------------------------------------------------------------------------- 1 | /*global describe:false, it:false, before:false, after:false */ 2 | /*jslint node: true */ 3 | 'use strict'; 4 | 5 | var svn = require('../'); 6 | 7 | var path = require('path'); 8 | var should = require('should'); 9 | var fs = require('fs-extra'); 10 | var createRepo = require('./createLocalSvnRepo.js' ); 11 | 12 | var testServer = ''; 13 | var nonExistantUrl = 'https://fud.com/svn/work'; 14 | var tempTestDir = 'temp'; 15 | var repoTestDir = path.join( tempTestDir, 'repo' ); 16 | var checkoutTestDir = path.join( tempTestDir, 'out_works' ); 17 | 18 | 19 | describe('node-svn-ultimate', function() { 20 | 21 | this.timeout( 60 * 1000 ); 22 | 23 | before( function( done ) { 24 | createRepo.create( repoTestDir, function( err, url ) { 25 | testServer = url; 26 | done( err ); 27 | } ); 28 | } ); 29 | 30 | after( function() { 31 | fs.removeSync( tempTestDir ); 32 | } ); 33 | 34 | 35 | describe('commands', function() { 36 | it('checkout', function(done) { 37 | svn.commands.checkout( testServer, checkoutTestDir, function( err ) { 38 | should.not.exist(err); 39 | done(); 40 | } ); 41 | }); 42 | it('checkout invalid url', function(done) { 43 | var p = path.join( tempTestDir, 'out_invalid' ); 44 | svn.commands.checkout( nonExistantUrl, p, { quiet: true }, function( err ) { 45 | should.exist(err); 46 | done(); 47 | } ); 48 | }); 49 | 50 | 51 | 52 | it('add', function(done) { 53 | var fp = path.join( checkoutTestDir, 'test2.txt' ); 54 | fs.writeFileSync( fp, 'aa' ); 55 | svn.commands.add( fp, function( err ) { 56 | should.not.exist(err); 57 | done(); 58 | } ); 59 | }); 60 | 61 | it('commit', function(done) { 62 | svn.commands.commit( checkoutTestDir, { msg: 'Added test2.txt' }, function( err ) { 63 | should.not.exist(err); 64 | done(); 65 | } ); 66 | }); 67 | 68 | it('status', function(done) { 69 | svn.commands.status( checkoutTestDir, function( err, data ) { 70 | should.not.exist(err); 71 | should.exist(data); 72 | done(); 73 | } ); 74 | }); 75 | 76 | // it('patch', function(done) { 77 | // var p = path.join( tempTestDir, 'test_patch.patch' ); 78 | // svn.commands.patch( p, function( err ) { 79 | // should.not.exist(err); 80 | // should.ok( fs.readFileSync( p ).toString() === 'aa' ); 81 | // done(); 82 | // } ); 83 | // }); 84 | 85 | it('revert', function(done) { 86 | var p = path.join( checkoutTestDir, 'test2.txt' ); 87 | fs.writeFileSync( p, 'bbbbbb' ); 88 | svn.commands.revert( p, function( err ) { 89 | should.not.exist(err); 90 | should.ok( fs.readFileSync( p ).toString() === 'aa' ); 91 | done(); 92 | } ); 93 | }); 94 | 95 | it('cat', function(done) { 96 | svn.commands.cat( testServer + '/test2.txt', function( err, data ) { 97 | should.not.exist(err); 98 | should.ok( data.length > 0 ); 99 | should.ok( data === 'aa' ); 100 | done(); 101 | } ); 102 | }); 103 | 104 | it('cat fails on non-existant file', function(done) { 105 | svn.commands.cat( testServer + '/bower2.json', { quiet: true }, function( err, data ) { 106 | should.exist(err); 107 | done(); 108 | } ); 109 | }); 110 | 111 | it('copy', function(done) { 112 | svn.commands.copy( testServer + '/test2.txt', testServer + '/test3.txt', 'Copy test2 to test3', function( err ) { 113 | should.not.exist(err); 114 | // make sure copied file exists 115 | svn.commands.cat( testServer + '/test3.txt', function( err2, data ) { 116 | should.not.exist(err2); 117 | should.ok( data.length > 0 ); 118 | should.ok( data === 'aa' ); 119 | done(); 120 | } ); 121 | } ); 122 | }); 123 | 124 | it('cleanup', function(done) { 125 | svn.commands.cleanup( checkoutTestDir, function( err, data ) { 126 | should.not.exist(err); 127 | done(); 128 | } ); 129 | }); 130 | 131 | it('log', function(done) { 132 | svn.commands.log( testServer, function( err, data ) { 133 | should.not.exist(err); 134 | should.exist(data); 135 | done(); 136 | } ); 137 | }); 138 | 139 | it('info', function(done) { 140 | svn.commands.info( testServer, function( err, data ) { 141 | should.not.exist(err); 142 | should.exist(data); 143 | done(); 144 | } ); 145 | }); 146 | 147 | it('merge', function(done) { 148 | svn.commands.info( testServer, function( err, data ) { 149 | var oldRev = data.entry.$.revision; 150 | var fp = path.join( checkoutTestDir, 'test2.txt' ); 151 | var oldContent = fs.readFileSync(fp); 152 | fs.writeFileSync( fp, 'aabb' ); 153 | svn.commands.commit( checkoutTestDir, { msg: 'Update test2.txt (aa -> aabb)' }, function( err ) { 154 | svn.commands.info( testServer, function( err, data ) { 155 | var newRev = data.entry.$.revision; 156 | var mergeOption = newRev + ':' + oldRev; 157 | svn.commands.merge( fp, { revision: mergeOption }, function( err ) { 158 | should.not.exist(err); 159 | svn.commands.commit( checkoutTestDir, { msg: 'Merged test2.txt ' }, function( err ) { 160 | var newContent = fs.readFileSync(fp); 161 | should.ok( newContent.toString('utf8') === oldContent.toString('utf8') ); 162 | done(); 163 | } ); 164 | } ); 165 | } ); 166 | } ); 167 | } ); 168 | }); 169 | 170 | it('mergeinfo: no option', function(done) { 171 | var src = testServer + '/test2.txt'; 172 | var target = testServer + '/test3.txt'; 173 | svn.commands.mergeinfo (src, target, function( err, data ) { 174 | should.not.exist(err); 175 | should.exist(data); // data graph 176 | done(); 177 | } ); 178 | }); 179 | 180 | it('mergeinfo: merged', function(done) { 181 | var src = testServer + '/test2.txt'; 182 | var target = testServer + '/test3.txt'; 183 | var opt = {showRevs: 'merged'}; 184 | svn.commands.mergeinfo (src, target, opt, function( err, data ) { 185 | should.not.exist(err); 186 | should.equal(data.length, 0); 187 | done(); 188 | } ); 189 | }); 190 | it('mergeinfo: eligible', function(done) { 191 | var src = testServer + '/test2.txt'; 192 | var target = testServer + '/test3.txt'; 193 | var opt = {showRevs: 'eligible'}; 194 | svn.commands.mergeinfo (src, target, opt, function( err, data ) { 195 | should.not.exist(err); 196 | should.ok( data.length > 0 ); 197 | should.equal( data, 'r3\nr4\n' ); 198 | done(); 199 | } ); 200 | }); 201 | 202 | it('list', function(done) { 203 | svn.commands.list( testServer, function( err, data ) { 204 | should.not.exist(err); 205 | should.exist(data); 206 | done(); 207 | } ); 208 | }); 209 | 210 | it('move', function(done) { 211 | svn.commands.move( testServer + '/test3.txt', testServer + '/test4.txt', 'Move test3.txt to test4.txt', function( err ) { 212 | should.not.exist(err); 213 | // make sure moved file exists 214 | svn.commands.cat( testServer + '/test4.txt', function( err2, data ) { 215 | should.not.exist(err2); 216 | should.ok( data.length > 0 ); 217 | should.ok( data === 'aa' ); 218 | // make sure old file no longer exists 219 | svn.commands.cat( testServer + '/test3.txt', function( err3, data ) { 220 | should.exist(err3); 221 | done(); 222 | } ); 223 | } ); 224 | } ); 225 | }); 226 | 227 | it('del', function(done) { 228 | svn.commands.del( testServer + '/test4.txt', 'Del text4.txt', function( err ) { 229 | should.not.exist(err); 230 | // make sure file deleted 231 | svn.commands.cat( testServer + '/test4.txt', function( err2, data ) { 232 | should.exist(err2); 233 | done(); 234 | } ); 235 | } ); 236 | }); 237 | 238 | it('export - single file', function(done) { 239 | var exportPath = path.join( tempTestDir, 'test2.txt' ); 240 | svn.commands.export( testServer + '/test2.txt', exportPath, function( err ) { 241 | should.not.exist(err); 242 | should.ok( fs.existsSync( exportPath ) ); 243 | should.ok( fs.readFileSync( exportPath ).toString() === 'aa' ); 244 | done(); 245 | } ); 246 | }); 247 | 248 | it('import - single file', function(done) { 249 | var importPath = path.join( tempTestDir, 'test2.txt' ); 250 | svn.commands.import( importPath, testServer + '/test20.txt', 'Import test2.txt to test20.txt', function( err ) { 251 | should.not.exist(err); 252 | svn.commands.cat( testServer + '/test20.txt', function( err2, data ) { 253 | should.not.exist(err2); 254 | should.ok( data.length > 0 ); 255 | should.ok( data === 'aa' ); 256 | done(); 257 | } ); 258 | } ); 259 | }); 260 | 261 | it('mucc - single mkdir command', function(done) { 262 | svn.commands.mucc( [ 'mkdir ' + testServer + '/testdir' ], 'mucc, Added testdir', function( err, data ) { 263 | should.not.exist(err); 264 | done(); 265 | } ); 266 | }); 267 | 268 | it('update working copy', function(done) { 269 | svn.commands.update( checkoutTestDir, function( err ) { 270 | should.not.exist(err); 271 | should.ok( fs.statSync( path.join( checkoutTestDir, 'test20.txt' ) ).isFile() ); 272 | should.ok( fs.statSync( path.join( checkoutTestDir, 'testdir' ) ).isDirectory() ); 273 | done(); 274 | } ); 275 | }); 276 | 277 | it('lock: local file in working copy', function(done) { 278 | var p = path.join( checkoutTestDir, 'test20.txt' ); 279 | svn.commands.lock( p, function( err, data ) { 280 | should.not.exist(err); 281 | // trying to delete the file should fail 282 | svn.commands.del( testServer + '/test20.txt', 'Del test20.txt (locked, fail)', function( err2 ) { 283 | should.exist(err2); 284 | done(); 285 | } ); 286 | } ); 287 | }); 288 | 289 | it('unlock: local file in working copy', function(done) { 290 | var p = path.join( checkoutTestDir, 'test20.txt' ); 291 | svn.commands.unlock( p, function( err, data ) { 292 | should.not.exist(err); 293 | // trying to delete the file should succeed 294 | svn.commands.del( testServer + '/test20.txt', 'Del test20.txt (unlocked, success)', function( err2 ) { 295 | should.not.exist(err2); 296 | done(); 297 | } ); 298 | } ); 299 | }); 300 | 301 | it('lock: remote file in repo', function(done) { 302 | svn.commands.lock( testServer + '/test2.txt', function( err, data ) { 303 | should.not.exist(err); 304 | // trying to delete the file should fail 305 | svn.commands.del( testServer + '/test2.txt', 'Del test2.txt (locked, fail)', function( err2 ) { 306 | should.exist(err2); 307 | done(); 308 | } ); 309 | } ); 310 | }); 311 | 312 | it('unlock: remote file in repo', function(done) { 313 | svn.commands.unlock( testServer + '/test2.txt', function( err, data ) { 314 | should.not.exist(err); 315 | // trying to delete the file should succeed 316 | svn.commands.del( testServer + '/test2.txt', 'Del test2.txt (unlocked, success)', function( err2 ) { 317 | should.not.exist(err2); 318 | done(); 319 | } ); 320 | } ); 321 | }); 322 | 323 | it('mkdir: working copy', function(done) { 324 | var p = path.join( checkoutTestDir, 'newdir' ); 325 | svn.commands.mkdir( p, function( err ) { 326 | should.not.exist(err); 327 | should.ok( fs.existsSync( p ) && fs.statSync( p ).isDirectory() ); 328 | done(); 329 | } ); 330 | }); 331 | it('mkdir: make array dir', function(done) { 332 | var paths = ['trunk', 'tags', 'branches'].map( function( p ) { 333 | return path.join( checkoutTestDir, p ); 334 | }); 335 | svn.commands.mkdir( paths, function( err ) { 336 | should.not.exist(err); 337 | should.ok( fs.existsSync( paths[0] ) && fs.existsSync( paths[2] ) ); 338 | should.ok( fs.statSync( paths[0] ).isDirectory() ); 339 | should.ok( fs.statSync( paths[2] ).isDirectory() ); 340 | done(); 341 | } ); 342 | }); 343 | 344 | it('mkdir: remote repo', function(done) { 345 | svn.commands.mkdir( testServer + '/newdir_remote', 'mkdir remote newdir_remote', function( err ) { 346 | should.not.exist(err); 347 | // delete dir after 348 | svn.commands.del( testServer + '/newdir_remote', 'Del newdir_remote', function( err2 ) { 349 | should.not.exist(err2); 350 | done(); 351 | } ); 352 | } ); 353 | }); 354 | 355 | it('upgrade', function(done) { 356 | // TODO: run svnadmin to create an old version repo, then upgrade it and verify its the new version 357 | svn.commands.upgrade( checkoutTestDir, function( err ) { 358 | should.not.exist(err); 359 | done(); 360 | } ); 361 | }); 362 | 363 | it('propset', function(done) { 364 | var p = path.join( checkoutTestDir, 'newdir' ); 365 | svn.commands.propset( 'testprop', 'myvalue', p, function( err ) { 366 | should.not.exist(err); 367 | done(); 368 | } ); 369 | }); 370 | 371 | it('propget', function(done) { 372 | var p = path.join( checkoutTestDir, 'newdir' ); 373 | svn.commands.propget( 'testprop', p, function( err, data ) { 374 | should.not.exist(err); 375 | should.exist(data); 376 | done(); 377 | } ); 378 | }); 379 | 380 | it('proplist', function(done) { 381 | var p = path.join( checkoutTestDir, 'newdir' ); 382 | svn.commands.proplist( p, function( err, data ) { 383 | should.not.exist(err); 384 | should.exist(data); 385 | done(); 386 | } ); 387 | }); 388 | 389 | it('propdel', function(done) { 390 | var p = path.join( checkoutTestDir, 'newdir' ); 391 | svn.commands.propdel( 'testprop', p, function( err ) { 392 | should.not.exist(err); 393 | done(); 394 | } ); 395 | }); 396 | 397 | // it('relocate', function(done) { 398 | // svn.commands.relocate( p, function( err, data ) { 399 | // should.not.exist(err); 400 | // done(); 401 | // } ); 402 | // }); 403 | 404 | // it('switch', function(done) { 405 | // svn.commands.switch( p, function( err, data ) { 406 | // should.not.exist(err); 407 | // done(); 408 | // } ); 409 | // }); 410 | }); 411 | 412 | describe( 'util', function() { 413 | 414 | it('getRevision path', function(done) { 415 | svn.util.getRevision( checkoutTestDir, function( err, data ) { 416 | should.not.exist(err); 417 | data.should.be.a.Number(); 418 | done(); 419 | } ); 420 | }); 421 | 422 | it('getRevision url', function(done) { 423 | svn.util.getRevision( testServer, function( err, data ) { 424 | should.not.exist(err); 425 | data.should.be.a.Number(); 426 | done(); 427 | } ); 428 | }); 429 | 430 | // it('getTags', function(done) { 431 | // svn.util.getTags( testServer, function( err, data ) { 432 | // should.not.exist(err); 433 | // data.should.be.instanceof( Array ); 434 | // done(); 435 | // } ); 436 | // }); 437 | 438 | // it('getLatestTag', function(done) { 439 | // svn.util.getLatestTag( testServer, function( err, data ) { 440 | // should.not.exist(err); 441 | // //console.log( data ); 442 | // done(); 443 | // } ); 444 | // }); 445 | } ); 446 | 447 | 448 | describe( 'SVNMucc helper', function() { 449 | it( 'put', function( done ) { 450 | var h = new svn.util.MuccHelper(); 451 | h.put( 'contents of new file', testServer + '/newfile.txt' ); 452 | h.commit( function( err ) { 453 | should.not.exist(err); 454 | done(); 455 | } ); 456 | } ); 457 | 458 | it( 'cp', function( done ) { 459 | var h = new svn.util.MuccHelper(); 460 | h.cp( testServer + '/newfile.txt', testServer + '/newfile2.txt' ); 461 | h.commit( function( err ) { 462 | should.not.exist(err); 463 | done(); 464 | } ); 465 | } ); 466 | } ); 467 | }); 468 | 469 | -------------------------------------------------------------------------------- /test/createLocalSvnRepo.js: -------------------------------------------------------------------------------- 1 | /*global describe:false, it:false */ 2 | /*jslint node: true */ 3 | // Creates a local SVN repositiory using svnadmin to run tests against 4 | 'use strict'; 5 | 6 | var svn = require('../'); 7 | 8 | var fs = require('fs-extra'); 9 | var path = require('path'); 10 | var spawn = require('child_process').spawn; 11 | 12 | 13 | exports.create = function( tempdir, callback ) { 14 | var dirname = 'temp_' + Math.floor( Math.random() * 999999 ); 15 | if ( fs.existsSync( tempdir ) ) { 16 | fs.removeSync( tempdir ); 17 | } 18 | fs.ensureDirSync( tempdir ); 19 | var proc = spawn( 'svnadmin', [ 'create', dirname ], { cwd: tempdir } ); 20 | proc.on( 'error', function( err ) { 21 | callback( err, '' ); 22 | } ); 23 | proc.on( 'exit', function() { 24 | var formattedPath = path.resolve( path.join( tempdir, dirname ) ); 25 | formattedPath = formattedPath.replace( /\\/g, '/' ); 26 | callback( null, 'file:///' + formattedPath ); 27 | } ); 28 | }; 29 | 30 | --------------------------------------------------------------------------------