git.js 6.7 KB

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