├── .gitignore ├── .DS_Store ├── src ├── .DS_Store ├── svn.js └── index.js ├── example ├── test.js └── index.js ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /example/index.js 3 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ooomelette/nodejs-svn/HEAD/.DS_Store -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ooomelette/nodejs-svn/HEAD/src/.DS_Store -------------------------------------------------------------------------------- /example/test.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | 4 | let pwd = path.dirname('svn://svn.code.anzogame.com:9528/service/'); 5 | 6 | console.log(pwd); 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs-svn", 3 | "version": "1.1.2", 4 | "description": "a version controller", 5 | "keywords": [ 6 | "svn", 7 | "subversion", 8 | "node", 9 | "svn interface" 10 | ], 11 | "main": "./src/index.js", 12 | "scripts": { 13 | "test": "echo \"Error: no test specified\" && exit 1" 14 | }, 15 | "author": { 16 | "name": "Ooomelette", 17 | "email": "yangbo_9@163.com" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/Ooomelette/nodejs-svn.git" 22 | }, 23 | "license": "ISC", 24 | "bugs": { 25 | "url": "https://github.com/Ooomelette/nodejs-svn/issues" 26 | }, 27 | "homepage": "https://github.com/Ooomelette/nodejs-svn#readme", 28 | "dependencies": { 29 | "xml2json": "^0.11.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | const SVN = require('../src/index'); 2 | const path = require('path') 3 | const svn = new SVN({ 4 | username: 'test', 5 | password: 'test123', 6 | root: 'svn://test/front/', 7 | debug: true, 8 | cwd: path.resolve(__dirname, './'), 9 | repoName: 'zyb_front', //仓库name 10 | }) 11 | 12 | // svn.info((err, data) => { 13 | // console.log('data', data); 14 | // }) 15 | 16 | 17 | // svn.list((err, data) => { 18 | // console.log('data', data); 19 | // }) 20 | 21 | svn.checkout('trunk', (err, data) => { 22 | console.log(11111) 23 | }) 24 | 25 | // svn.list('branches', (err, data) => { 26 | // console.log('svn branches: ', data) 27 | 28 | // }) 29 | 30 | // svn.switch('branches/20170928-ugc', '../example/trunk/', (err, data) => { 31 | // console.log('data', data); 32 | // console.log('err', err) 33 | // }) 34 | 35 | svn.cleanup((err, data) => { 36 | console.log('err', err) 37 | console.log('data', data) 38 | }) -------------------------------------------------------------------------------- /src/svn.js: -------------------------------------------------------------------------------- 1 | // svn 核心方法 2 | 3 | const spawn = require('child_process').spawn 4 | const path = require('path') 5 | class SVN { 6 | constructor(config) { 7 | let { password = '', username = '', root = '', debug = false, cwd = path.resolve(__dirname)} = config 8 | this.password = password 9 | this.username = username 10 | this.root = root 11 | this.queues = [] 12 | this.isRuning = false 13 | this.debug = debug 14 | this.cwd = cwd 15 | } 16 | 17 | run() { 18 | let _this = this; 19 | if (this.isRuning || this.queues.length === 0) { 20 | return 21 | } 22 | this.isRuning = true 23 | 24 | let params = this.queues.shift(), 25 | text = '', 26 | err = null; 27 | 28 | let callback = params.callback; 29 | 30 | if (this.debug) { 31 | console.log('[SVN COMMAND]: ' + params.command + ' ' + params.args.join(' ')) 32 | } 33 | let proc = spawn(params.command, params.args, params.options); 34 | 35 | // 执行成功 36 | proc.stdout.on('data', (data) => { 37 | text += data 38 | }) 39 | 40 | //执行失败 41 | proc.stderr.on('data', (data) => { 42 | data = String(data) 43 | err = new Error(data) 44 | }) 45 | 46 | // 进程错误 47 | proc.on('error', (error) => { 48 | var result = null, 49 | err = new Error('[SVN ERROR:404] svn command not found') 50 | if (error.code === 'ENOENT' && callback) { 51 | callback(err, text, params) 52 | } 53 | this.isRuning = false 54 | this.run() 55 | }) 56 | 57 | // 进程关闭 58 | proc.on('close', (code) => { 59 | callback && callback(err, text, params) 60 | this.isRuning = false 61 | this.run() 62 | }) 63 | } 64 | 65 | pushQueue(cmds) { 66 | let { command = '', args = [], options = {}, callback = null, xml = false} = cmds; 67 | 68 | if (callback && typeof callback !== 'function') { 69 | console.log('callback 参数必须是一个函数,而不是一个' + typeof callback); 70 | return false 71 | } 72 | 73 | let opts = { 74 | command: 'svn', 75 | args: [command].concat(args), 76 | options: Object.assign({cwd: this.cwd}, options), 77 | xml: xml, 78 | callback: callback 79 | } 80 | 81 | if (opts.xml) { 82 | opts.args = opts.args.concat(['--xml']); 83 | } 84 | 85 | opts.args = opts.args.concat(['--non-interactive', '--trust-server-cert']) 86 | 87 | if (this.username && this.password) { 88 | opts.args = opts.args.concat(['--username', this.username, '--password', this.password]) 89 | } 90 | 91 | this.queues.push(opts) 92 | } 93 | } 94 | 95 | module.exports = SVN -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # nodejs-svn 4 | nodejs-svn是svn在nodejs中的实现,旨在用nodejs控制版本,我用于线上的自动化构建工具。它由2部分组成,svnjs和indexjs,svnjs是nodejs实现svn的核心,它只包含核心的方法。相对独立。indexjs是对svn命令行的扩展,封装了一些常用的api。该库由es6的class编写,你可以根据自己的喜好和需求重新进行封装,当然这个要求你对[svn命令行](http://www.riaoo.com/subpages/svn_cmd_reference.html)有一定的了解。 5 | 6 | 7 | # Install 8 | 9 | 10 | ```code 11 | npm install nodejs-svn -S 12 | 13 | ``` 14 | 15 | 16 | # Usage 17 | 18 | ```code 19 | var SVN = require('node-svn'); 20 | var svn = new SVN({ 21 | username: '你的svn用户名', 22 | passwork: '你的svn密码', 23 | root: '你的svn路径,就是trunk和branches的路径', 24 | debug: true, // 是否在控制台打印调试信息 25 | cwd: path.resolve(__dirname, './'), //仓库存放的目录 26 | repoName: 'zyb_front', //仓库name 27 | }); 28 | ``` 29 | 30 | 在new svn的时候,需要传入config对象,这个对象是必须的,其中svn的命令是在你填写的repoName中执行,如果您有额外的需求,可以使用通用方法command(),它是svn的核心方法。 31 | 32 | 在初始化之后,你就可以进行svn操作了 33 | 34 | 比如: 35 | 36 | ```code 37 | svn.commit('-m "这是描述文本"', (err, res) => { 38 | console.log('成功后的回掉函数!') 39 | }) 40 | ``` 41 | 42 | 43 | # API 44 | 45 | - callback 46 | 统一的回掉,这个回掉函数会传入2个参数,err和res, err存在于,res是执行命令subversion命令返回的结果 47 | 48 | 49 | - svn.commit('-m "descript"', callback) 50 | 接收2两个参数,第一个参数是-m操作符和提交描述,中间必须用“空格”隔开,第二个参数为执行命令后的回掉 51 | 52 | ```code 53 | eg: 54 | svn.commit('-m "descript"', callback) 55 | 56 | ``` 57 | 58 | 59 | - svn.info(branches,callback) 60 | 接收最多2个参数,第一个参数是分支信息(可以为远端信息,也可以是本地库信息,不传默认本地库信息并且在root + repoName 目录下),第二个参数是回掉函数。 61 | 62 | ```code 63 | eg: 64 | svn.info(callback); 65 | svn.info('svn://test/repo', callback) 66 | svn.info(path.resolve(__dirname, './'), callback) 67 | ``` 68 | 69 | - svn.checkout 70 | 接受2个参数,第一个是必须参数->分支,第二个参数是回掉函数->callback,分支检出在repoName文件夹下,目录为cwd 71 | 72 | ```code 73 | eg: 74 | svn.checkout('branches/test-branches', callback) 75 | svn.checkout('trunk', (err, data) => {}) 76 | ``` 77 | 78 | 79 | 80 | - svn.list 81 | 接收最多2个参数,第一个参数是分支信息(可以为远端信息,也可以是本地库信息,不传默认本地库信息并且在root + repoName 目录下),第二个参数是回掉函数。 82 | 83 | ```code 84 | eg: 85 | svn.list(callback); 86 | svn.list('svn://test/repo', callback) 87 | svn.list(path.resolve(__dirname, './'), callback) 88 | 89 | ``` 90 | 91 | 92 | - svn.switch 93 | 接收最多2个参数,第一个参数是需要切换的分支名字,第二个参数是回掉函数。 94 | 95 | ```code 96 | eg: 97 | svn.switch('trunk'); 98 | svn.list('branches/test', callback) 99 | 100 | ``` 101 | 102 | 103 | - svn.cleanup 104 | 接收最多1个回掉函数。 105 | 106 | ```code 107 | eg: 108 | svn.cleanup(callback); 109 | 110 | ``` 111 | 112 | 113 | ## 核心方法 114 | - svn.command() 115 | 接受一个options对象,这个对象会对象包含以下信息: 116 | 117 | ```code 118 | options: { 119 | command: '', // 需要执行的命令 比如:switch 120 | args: [], // 执行svn命令的参数,比如: list --xml --username xxx --user password *** 121 | options: { // 运行spawn的一些参数,具体可参考nodejs文档 122 | cwd: path.resolve(this.cwd, this.repoName) //执行command所在的目录 123 | } 124 | } 125 | ``` 126 | 127 | 128 | 129 | # 注意 130 | 路径是字符串相加,请保证路径的正确性,特别注意root和branches的路径组合,路径请使用相对于root的路径 131 | nodejs参考文档: http://nodejs.cn/api/child_process.html#child_process_child_process_spawn_command_args_options 132 | 133 | 134 | ## 后记 135 | 如果你还满意,不妨给我一颗star,这将成为我的动力!ths! 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const svn = require('./svn') 4 | const parser = require('xml2json') 5 | const path = require('path') 6 | 7 | class subverion extends svn { 8 | constructor(config) { 9 | super(config) 10 | let {cwd = path.resolve(__dirname), repoName = 'SVN'} = config 11 | this.cwd = cwd 12 | this.repoName = repoName 13 | this.repoPath = cwd + repoName 14 | } 15 | 16 | //svn.commit('-m descript', callback) 17 | commit(args, callback) { 18 | if (!args || typeof args !== 'string' || (typeof args === 'string' && args.indexOf(' ') === -1)) { 19 | throw new Error('[SVN ERROR:404] svn commit command desc err, please input again') 20 | return false 21 | } 22 | let index = args.indexOf(' ') 23 | let operate = args.slice(0, index) 24 | let desc = args.slice(index + 1) 25 | let opt = Object.assign({ args: [operate, desc] }, { command: 'commit' }) 26 | this.command(opt) 27 | } 28 | 29 | //svn.info('http://rep/', callback) 30 | info() { 31 | let path = this.repoPath 32 | let params = this.parseArgs(Array.from(arguments)) 33 | 34 | let handleData = (err, data) => { 35 | params.callback && params.callback(err, data.info.entry); 36 | } 37 | if (typeof params.path === 'string' && params !== '') { 38 | path = params.path 39 | } 40 | 41 | 42 | let opt = Object.assign({ args: [path], callback: handleData, xml: true }, { command: 'info' }) 43 | this.command(opt) 44 | } 45 | 46 | // svn.checkout('trunk', './', ()=>{}) 47 | checkout() { 48 | let argument = Array.from(arguments); 49 | 50 | if (argument.length === 0 || (!argument[0] && typeof argument[0] !== 'string')) { 51 | throw new Error('[SVN ERROR:404] checkout input err, please input again') 52 | return false 53 | } 54 | 55 | let branches = this.root + argument[0], 56 | handleData = (err, data) => { 57 | if (!err) { 58 | data = 'success' 59 | } 60 | typeof arguments[1] === 'function' && arguments[1](err, data); 61 | }; 62 | 63 | let opt = Object.assign({ args: [branches, this.repoName], callback: handleData, options: {cwd: this.cwd} }, { command: 'checkout' }) 64 | this.command(opt) 65 | } 66 | 67 | //svn.list('http://rep/', callback) 68 | list() { 69 | let path = this.repoPath 70 | let params = this.parseArgs(Array.from(arguments)) 71 | let handleData = (err, data) => { 72 | params.callback && params.callback(err, data.lists.list) 73 | } 74 | if (typeof params.path === 'string' && params !== '') { 75 | path = params.path 76 | } 77 | 78 | let opt = Object.assign({ args: [path], callback: handleData, xml: true }, { command: 'list' }) 79 | this.command(opt) 80 | } 81 | 82 | // svn.switch('./branches/test') 83 | switch (branches, callback) { 84 | if (typeof branches !== 'string') { 85 | throw new Error('[SVN ERROR:404] switch params err, please input again') 86 | return false 87 | } 88 | let branch = branches === 'trunk' ? 'trunk' : 'branches/' + branches 89 | let opt = Object.assign({ 90 | args: [this.root + branch], 91 | callback: callback, 92 | }, { command: 'switch' }) 93 | this.command(opt) 94 | } 95 | 96 | update(callback) { 97 | let opt = Object.assign({ 98 | args: [], 99 | callback: callback 100 | }, { command: 'update' }) 101 | this.command(opt) 102 | } 103 | 104 | cleanup() { 105 | let params = this.parseArgs(Array.from(arguments)) 106 | let handleData = (err, data) => { 107 | let result = null; 108 | if (!err) { 109 | result = { 110 | message: 'success', 111 | data: data 112 | } 113 | } 114 | params.callback && params.callback(err, result) 115 | } 116 | let opt = Object.assign( {callback: handleData }, { command: 'cleanup' }) 117 | this.command(opt) 118 | } 119 | 120 | // core 121 | command(opts) { 122 | let needxml = opts.xml 123 | // 处理返回的xml 124 | let callback = (err, data) => { 125 | 126 | if (typeof opts.callback === 'function') { 127 | let result = data 128 | if (needxml) { 129 | result = data ? this.string2json(data) : null 130 | } 131 | opts.callback(err, result) 132 | } 133 | } 134 | 135 | let opt = Object.assign({ 136 | command: '', 137 | args: [], 138 | options: { 139 | cwd: path.resolve(this.cwd, this.repoName) //执行command所在的目录 140 | } 141 | }, opts, { callback: callback }) 142 | 143 | if (!opt.command) { 144 | throw new Error('[SVN ERROR:404] svn command not found') 145 | return false 146 | } 147 | 148 | if (this.debug) { 149 | console.log('[SVN Queue]: ', opt) 150 | } 151 | this.pushQueue(opt) 152 | this.run() 153 | } 154 | 155 | string2json(str) { 156 | let result = parser.toJson(str) 157 | return JSON.parse(result) 158 | } 159 | 160 | parseArgs(arr) { 161 | let len = arr.length, 162 | path = '', 163 | callback = null; 164 | 165 | switch (len) { 166 | case 0: 167 | { 168 | break; 169 | } 170 | case 1: 171 | { 172 | if (typeof arr[0] === 'string') { 173 | path = arr[0]; 174 | } else if (typeof arr[0] === 'function') { 175 | callback = arr[0] 176 | } 177 | break; 178 | } 179 | // 大于等于2 180 | default: 181 | { 182 | path = arr[0]; 183 | callback = arr[1]; 184 | } 185 | } 186 | 187 | return { 188 | path: path, 189 | callback: callback 190 | } 191 | } 192 | } 193 | 194 | module.exports = subverion; --------------------------------------------------------------------------------