git.js 5.8 KB

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