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.

258 lines
5.9 KiB

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