git.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  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. urlObj.auth = appconfig.git.auth.username + ((appconfig.git.auth.type !== 'ssh') ? ':' + appconfig.git.auth.password : '')
  75. self._url = URL.format(urlObj)
  76. return self._git.exec('remote', 'show').then((cProc) => {
  77. let out = cProc.stdout.toString()
  78. if (_.includes(out, 'origin')) {
  79. return true
  80. } else {
  81. return Promise.join(
  82. self._git.exec('config', ['--local', 'user.name', self._signature.name]),
  83. self._git.exec('config', ['--local', 'user.email', self._signature.email])
  84. ).then(() => {
  85. return self._git.exec('remote', ['add', 'origin', self._url])
  86. })
  87. }
  88. })
  89. }).catch((err) => {
  90. winston.error('[' + PROCNAME + '][GIT] Git remote error!')
  91. throw err
  92. }).then(() => {
  93. winston.info('[' + PROCNAME + '][GIT] Git repository is OK.')
  94. return true
  95. })
  96. },
  97. /**
  98. * Gets the repo path.
  99. *
  100. * @return {String} The repo path.
  101. */
  102. getRepoPath () {
  103. return this._repo.path || path.join(ROOTPATH, 'repo')
  104. },
  105. /**
  106. * Sync with the remote repository
  107. *
  108. * @return {Promise} Resolve on sync success
  109. */
  110. resync () {
  111. let self = this
  112. // Fetch
  113. winston.info('[' + PROCNAME + '][GIT] Performing pull from remote repository...')
  114. return self._git.pull('origin', self._repo.branch).then((cProc) => {
  115. winston.info('[' + PROCNAME + '][GIT] Pull completed.')
  116. })
  117. .catch((err) => {
  118. winston.error('[' + PROCNAME + '][GIT] Unable to fetch from git origin!')
  119. throw err
  120. })
  121. .then(() => {
  122. // Check for changes
  123. return self._git.exec('log', 'origin/' + self._repo.branch + '..HEAD').then((cProc) => {
  124. let out = cProc.stdout.toString()
  125. if (_.includes(out, 'commit')) {
  126. winston.info('[' + PROCNAME + '][GIT] Performing push to remote repository...')
  127. return self._git.push('origin', self._repo.branch).then(() => {
  128. return winston.info('[' + PROCNAME + '][GIT] Push completed.')
  129. })
  130. } else {
  131. winston.info('[' + PROCNAME + '][GIT] Push skipped. Repository is already in sync.')
  132. }
  133. return true
  134. })
  135. })
  136. .catch((err) => {
  137. winston.error('[' + PROCNAME + '][GIT] Unable to push changes to remote!')
  138. throw err
  139. })
  140. },
  141. /**
  142. * Commits a document.
  143. *
  144. * @param {String} entryPath The entry path
  145. * @return {Promise} Resolve on commit success
  146. */
  147. commitDocument (entryPath) {
  148. let self = this
  149. let gitFilePath = entryPath + '.md'
  150. let commitMsg = ''
  151. return self._git.exec('ls-files', gitFilePath).then((cProc) => {
  152. let out = cProc.stdout.toString()
  153. return _.includes(out, gitFilePath)
  154. }).then((isTracked) => {
  155. commitMsg = (isTracked) ? 'Updated ' + gitFilePath : 'Added ' + gitFilePath
  156. return self._git.add(gitFilePath)
  157. }).then(() => {
  158. return self._git.commit(commitMsg).catch((err) => {
  159. if (_.includes(err.stdout, 'nothing to commit')) { return true }
  160. })
  161. })
  162. },
  163. /**
  164. * Move a document.
  165. *
  166. * @param {String} entryPath The current entry path
  167. * @param {String} newEntryPath The new entry path
  168. * @return {Promise<Boolean>} Resolve on success
  169. */
  170. moveDocument (entryPath, newEntryPath) {
  171. let self = this
  172. let gitFilePath = entryPath + '.md'
  173. let gitNewFilePath = newEntryPath + '.md'
  174. return self._git.exec('mv', [gitFilePath, gitNewFilePath]).then((cProc) => {
  175. let out = cProc.stdout.toString()
  176. if (_.includes(out, 'fatal')) {
  177. let errorMsg = _.capitalize(_.head(_.split(_.replace(out, 'fatal: ', ''), ',')))
  178. throw new Error(errorMsg)
  179. }
  180. return true
  181. })
  182. },
  183. /**
  184. * Commits uploads changes.
  185. *
  186. * @param {String} msg The commit message
  187. * @return {Promise} Resolve on commit success
  188. */
  189. commitUploads (msg) {
  190. let self = this
  191. msg = msg || 'Uploads repository sync'
  192. return self._git.add('uploads').then(() => {
  193. return self._git.commit(msg).catch((err) => {
  194. if (_.includes(err.stdout, 'nothing to commit')) { return true }
  195. })
  196. })
  197. }
  198. }