You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

257 lines
7.1 KiB

  1. 'use strict'
  2. const Git = require('git-wrapper2-promise')
  3. const Promise = require('bluebird')
  4. const path = require('path')
  5. const fs = Promise.promisifyAll(require('fs'))
  6. const _ = require('lodash')
  7. const URL = require('url')
  8. /**
  9. * Git Model
  10. */
  11. module.exports = {
  12. _git: null,
  13. _url: '',
  14. _repo: {
  15. path: '',
  16. branch: 'master',
  17. exists: false
  18. },
  19. _signature: {
  20. name: 'Wiki',
  21. email: 'user@example.com'
  22. },
  23. _opts: {
  24. clone: {},
  25. push: {}
  26. },
  27. onReady: null,
  28. /**
  29. * Initialize Git model
  30. *
  31. * @return {Object} Git model instance
  32. */
  33. init () {
  34. let self = this
  35. // -> Build repository path
  36. if (_.isEmpty(appconfig.paths.repo)) {
  37. self._repo.path = path.join(ROOTPATH, 'repo')
  38. } else {
  39. self._repo.path = appconfig.paths.repo
  40. }
  41. // -> Initialize repository
  42. self.onReady = self._initRepo(appconfig)
  43. // Define signature
  44. if (appconfig.git) {
  45. self._signature.name = appconfig.git.signature.name || 'Wiki'
  46. self._signature.email = appconfig.git.signature.email || 'user@example.com'
  47. }
  48. return self
  49. },
  50. /**
  51. * Initialize Git repository
  52. *
  53. * @param {Object} appconfig The application config
  54. * @return {Object} Promise
  55. */
  56. _initRepo (appconfig) {
  57. let self = this
  58. winston.info('[' + PROCNAME + '.Git] Checking Git repository...')
  59. // -> Check if path is accessible
  60. return fs.mkdirAsync(self._repo.path).catch((err) => {
  61. if (err.code !== 'EEXIST') {
  62. winston.error('[' + PROCNAME + '.Git] Invalid Git repository path or missing permissions.')
  63. }
  64. }).then(() => {
  65. self._git = new Git({ 'git-dir': self._repo.path })
  66. // -> Check if path already contains a git working folder
  67. return self._git.isRepo().then((isRepo) => {
  68. self._repo.exists = isRepo
  69. return (!isRepo) ? self._git.exec('init') : true
  70. }).catch((err) => { // eslint-disable-line handle-callback-err
  71. self._repo.exists = false
  72. })
  73. }).then(() => {
  74. if (appconfig.git === false) {
  75. winston.info('[' + PROCNAME + '.Git] Remote syncing is disabled. Not recommended!')
  76. return Promise.resolve(true)
  77. }
  78. // Initialize remote
  79. let urlObj = URL.parse(appconfig.git.url)
  80. if (appconfig.git.auth.type !== 'ssh') {
  81. urlObj.auth = appconfig.git.auth.username + ':' + appconfig.git.auth.password
  82. }
  83. self._url = URL.format(urlObj)
  84. let gitConfigs = [
  85. () => { return self._git.exec('config', ['--local', 'user.name', self._signature.name]) },
  86. () => { return self._git.exec('config', ['--local', 'user.email', self._signature.email]) },
  87. () => { return self._git.exec('config', ['--local', '--bool', 'http.sslVerify', _.toString(appconfig.git.auth.sslVerify)]) }
  88. ]
  89. if (appconfig.git.auth.type === 'ssh') {
  90. gitConfigs.push(() => {
  91. return self._git.exec('config', ['--local', 'core.sshCommand', 'ssh -i "' + appconfig.git.auth.privateKey + '" -o StrictHostKeyChecking=no'])
  92. })
  93. }
  94. return self._git.exec('remote', 'show').then((cProc) => {
  95. let out = cProc.stdout.toString()
  96. return Promise.each(gitConfigs, fn => { return fn() }).then(() => {
  97. if (!_.includes(out, 'origin')) {
  98. return self._git.exec('remote', ['add', 'origin', self._url])
  99. } else {
  100. return self._git.exec('remote', ['set-url', 'origin', self._url])
  101. }
  102. }).catch(err => {
  103. winston.error(err)
  104. })
  105. })
  106. }).catch((err) => {
  107. winston.error('[' + PROCNAME + '.Git] Git remote error!')
  108. throw err
  109. }).then(() => {
  110. winston.info('[' + PROCNAME + '.Git] Git repository is OK.')
  111. return true
  112. })
  113. },
  114. /**
  115. * Gets the repo path.
  116. *
  117. * @return {String} The repo path.
  118. */
  119. getRepoPath () {
  120. return this._repo.path || path.join(ROOTPATH, 'repo')
  121. },
  122. /**
  123. * Sync with the remote repository
  124. *
  125. * @return {Promise} Resolve on sync success
  126. */
  127. resync () {
  128. let self = this
  129. // Is git remote disabled?
  130. if (appconfig.git === false) {
  131. return Promise.resolve(true)
  132. }
  133. // Fetch
  134. winston.info('[' + PROCNAME + '.Git] Performing pull from remote repository...')
  135. return self._git.pull('origin', self._repo.branch).then((cProc) => {
  136. winston.info('[' + PROCNAME + '.Git] Pull completed.')
  137. })
  138. .catch((err) => {
  139. winston.error('[' + PROCNAME + '.Git] Unable to fetch from git origin!')
  140. throw err
  141. })
  142. .then(() => {
  143. // Check for changes
  144. return self._git.exec('log', 'origin/' + self._repo.branch + '..HEAD').then((cProc) => {
  145. let out = cProc.stdout.toString()
  146. if (_.includes(out, 'commit')) {
  147. winston.info('[' + PROCNAME + '.Git] Performing push to remote repository...')
  148. return self._git.push('origin', self._repo.branch).then(() => {
  149. return winston.info('[' + PROCNAME + '.Git] Push completed.')
  150. })
  151. } else {
  152. winston.info('[' + PROCNAME + '.Git] Push skipped. Repository is already in sync.')
  153. }
  154. return true
  155. })
  156. })
  157. .catch((err) => {
  158. winston.error('[' + PROCNAME + '.Git] Unable to push changes to remote!')
  159. throw err
  160. })
  161. },
  162. /**
  163. * Commits a document.
  164. *
  165. * @param {String} entryPath The entry path
  166. * @return {Promise} Resolve on commit success
  167. */
  168. commitDocument (entryPath) {
  169. let self = this
  170. let gitFilePath = entryPath + '.md'
  171. let commitMsg = ''
  172. return self._git.exec('ls-files', gitFilePath).then((cProc) => {
  173. let out = cProc.stdout.toString()
  174. return _.includes(out, gitFilePath)
  175. }).then((isTracked) => {
  176. commitMsg = (isTracked) ? 'Updated ' + gitFilePath : 'Added ' + gitFilePath
  177. return self._git.add(gitFilePath)
  178. }).then(() => {
  179. return self._git.commit(commitMsg).catch((err) => {
  180. if (_.includes(err.stdout, 'nothing to commit')) { return true }
  181. })
  182. })
  183. },
  184. /**
  185. * Move a document.
  186. *
  187. * @param {String} entryPath The current entry path
  188. * @param {String} newEntryPath The new entry path
  189. * @return {Promise<Boolean>} Resolve on success
  190. */
  191. moveDocument (entryPath, newEntryPath) {
  192. let self = this
  193. let gitFilePath = entryPath + '.md'
  194. let gitNewFilePath = newEntryPath + '.md'
  195. return self._git.exec('mv', [gitFilePath, gitNewFilePath]).then((cProc) => {
  196. let out = cProc.stdout.toString()
  197. if (_.includes(out, 'fatal')) {
  198. let errorMsg = _.capitalize(_.head(_.split(_.replace(out, 'fatal: ', ''), ',')))
  199. throw new Error(errorMsg)
  200. }
  201. return true
  202. })
  203. },
  204. /**
  205. * Commits uploads changes.
  206. *
  207. * @param {String} msg The commit message
  208. * @return {Promise} Resolve on commit success
  209. */
  210. commitUploads (msg) {
  211. let self = this
  212. msg = msg || 'Uploads repository sync'
  213. return self._git.add('uploads').then(() => {
  214. return self._git.commit(msg).catch((err) => {
  215. if (_.includes(err.stdout, 'nothing to commit')) { return true }
  216. })
  217. })
  218. }
  219. }