gitlatex 2.0.1__tar.gz → 2.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. {gitlatex-2.0.1/gitlatex.egg-info → gitlatex-2.1.0}/PKG-INFO +6 -2
  2. {gitlatex-2.0.1 → gitlatex-2.1.0}/README.md +5 -1
  3. gitlatex-2.1.0/docs/sync-and-conflicts.md +206 -0
  4. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/_version.py +3 -3
  5. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/routes/__init__.py +3 -0
  6. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/routes/git.py +32 -63
  7. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/routes/repos.py +4 -1
  8. gitlatex-2.1.0/gitlatex/routes/scm.py +221 -0
  9. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/routes/workspace.py +2 -0
  10. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/services/comments.py +83 -1
  11. gitlatex-2.1.0/gitlatex/services/comments_merge.py +106 -0
  12. gitlatex-2.1.0/gitlatex/services/scm.py +406 -0
  13. gitlatex-2.1.0/gitlatex/services/sync.py +168 -0
  14. gitlatex-2.0.1/gitlatex/ui/assets/PdfViewer-CSeWHO6L.js → gitlatex-2.1.0/gitlatex/ui/assets/PdfViewer-CYPYydmG.js +2 -2
  15. gitlatex-2.1.0/gitlatex/ui/assets/index-Bd4rVZK8.css +1 -0
  16. gitlatex-2.1.0/gitlatex/ui/assets/index-DL-0rlAe.js +2179 -0
  17. gitlatex-2.0.1/gitlatex/ui/assets/monaco-vim.umd-TnvlvL5k.js → gitlatex-2.1.0/gitlatex/ui/assets/monaco-vim.umd-DBJdb7VY.js +2 -2
  18. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/index.html +2 -2
  19. {gitlatex-2.0.1 → gitlatex-2.1.0/gitlatex.egg-info}/PKG-INFO +6 -2
  20. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex.egg-info/SOURCES.txt +12 -5
  21. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex.egg-info/scm_file_list.json +13 -0
  22. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex.egg-info/scm_version.json +2 -2
  23. gitlatex-2.1.0/tests/test_scm.py +266 -0
  24. gitlatex-2.1.0/tests/test_sync.py +235 -0
  25. gitlatex-2.0.1/gitlatex/ui/assets/index-CRd_ihCz.js +0 -1938
  26. gitlatex-2.0.1/gitlatex/ui/assets/index-DzQbJ7aZ.css +0 -1
  27. {gitlatex-2.0.1 → gitlatex-2.1.0}/.github/workflows/publish.yml +0 -0
  28. {gitlatex-2.0.1 → gitlatex-2.1.0}/.gitignore +0 -0
  29. {gitlatex-2.0.1 → gitlatex-2.1.0}/MANIFEST.in +0 -0
  30. {gitlatex-2.0.1 → gitlatex-2.1.0}/docs/vim-mode.md +0 -0
  31. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/__init__.py +0 -0
  32. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/__main__.py +0 -0
  33. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/app.py +0 -0
  34. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/engine/manifest.json +0 -0
  35. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/http.py +0 -0
  36. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/app.js +0 -0
  37. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/bib-commands.json +0 -0
  38. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/css/base.css +0 -0
  39. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/css/console.css +0 -0
  40. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/css/docs.css +0 -0
  41. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/css/editor.css +0 -0
  42. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/css/home.css +0 -0
  43. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/css/layout.css +0 -0
  44. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/css/loading.css +0 -0
  45. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/css/outline.css +0 -0
  46. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/css/problems.css +0 -0
  47. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/css/review.css +0 -0
  48. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/css/settings.css +0 -0
  49. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/css/sidebar.css +0 -0
  50. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/css/support.css +0 -0
  51. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/css/toolbar.css +0 -0
  52. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/css/updates.css +0 -0
  53. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/css/versions.css +0 -0
  54. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/favicon.svg +0 -0
  55. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/index.html +0 -0
  56. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/build/compile.js +0 -0
  57. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/build/problems.js +0 -0
  58. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/core/api.js +0 -0
  59. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/core/filetypes.js +0 -0
  60. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/core/router.js +0 -0
  61. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/core/state.js +0 -0
  62. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/core/storage.js +0 -0
  63. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/editor/completions.js +0 -0
  64. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/editor/envcolors.js +0 -0
  65. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/editor/filetree.js +0 -0
  66. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/editor/languages.js +0 -0
  67. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/editor/mainfile.js +0 -0
  68. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/editor/monaco.js +0 -0
  69. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/editor/outline.js +0 -0
  70. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/editor/projectindex.js +0 -0
  71. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/editor/review.js +0 -0
  72. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/editor/session.js +0 -0
  73. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/editor/snippets.js +0 -0
  74. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/editor/spell.js +0 -0
  75. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/editor/synctex.js +0 -0
  76. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/editor/vim.js +0 -0
  77. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/editor/vimtex.js +0 -0
  78. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/git/actions.js +0 -0
  79. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/git/diffview.js +0 -0
  80. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/git/menu.js +0 -0
  81. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/git/versions.js +0 -0
  82. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/home/repolist.js +0 -0
  83. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/ui/consolepane.js +0 -0
  84. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/ui/layout.js +0 -0
  85. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/ui/loading.js +0 -0
  86. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/ui/modals.js +0 -0
  87. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/ui/pdfviewer.js +0 -0
  88. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/ui/settings.js +0 -0
  89. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/ui/theme.js +0 -0
  90. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/js/ui/viewer.js +0 -0
  91. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/public/latex-commands.json +0 -0
  92. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/routes/compile.py +0 -0
  93. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/routes/engine.py +0 -0
  94. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/routes/files.py +0 -0
  95. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/routes/pages.py +0 -0
  96. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/routes/projectindex.py +0 -0
  97. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/routes/review.py +0 -0
  98. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/routes/snippets.py +0 -0
  99. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/routes/spell.py +0 -0
  100. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/routes/synctex.py +0 -0
  101. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/routes/system.py +0 -0
  102. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/server.py +0 -0
  103. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/services/__init__.py +0 -0
  104. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/services/git_backend.py +0 -0
  105. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/services/gitrepo.py +0 -0
  106. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/services/latex.py +0 -0
  107. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/services/paths.py +0 -0
  108. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/services/projectindex.py +0 -0
  109. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/services/snippets.py +0 -0
  110. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/services/spell.py +0 -0
  111. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/services/synctex.py +0 -0
  112. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/services/synctex_reader.py +0 -0
  113. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/services/updates.py +0 -0
  114. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/state.py +0 -0
  115. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  116. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  117. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  118. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  119. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  120. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  121. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  122. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  123. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  124. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  125. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  126. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  127. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  128. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  129. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  130. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  131. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  132. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  133. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  134. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  135. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  136. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  137. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  138. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  139. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  140. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  141. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  142. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  143. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  144. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  145. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  146. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  147. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  148. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  149. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  150. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  151. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  152. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  153. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  154. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  155. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  156. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  157. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  158. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  159. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  160. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  161. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  162. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  163. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  164. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  165. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  166. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  167. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  168. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  169. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  170. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  171. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  172. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  173. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  174. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/codicon-DF1abBS2.ttf +0 -0
  175. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/editor.worker-Yd1NR0QT.js +0 -0
  176. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/material-symbols-outlined-DzyP15l8.woff2 +0 -0
  177. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/pdf.worker.min-BmVo14Nb.mjs +0 -0
  178. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/assets/tex.worker-5oo4mY5v.js +0 -0
  179. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex/ui/favicon.svg +0 -0
  180. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex.egg-info/dependency_links.txt +0 -0
  181. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex.egg-info/entry_points.txt +0 -0
  182. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex.egg-info/requires.txt +0 -0
  183. {gitlatex-2.0.1 → gitlatex-2.1.0}/gitlatex.egg-info/top_level.txt +0 -0
  184. {gitlatex-2.0.1 → gitlatex-2.1.0}/pyproject.toml +0 -0
  185. {gitlatex-2.0.1 → gitlatex-2.1.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitlatex
3
- Version: 2.0.1
3
+ Version: 2.1.0
4
4
  Summary: A simple UI for LaTeX projects with Git support. Clone repos, edit, compile, and push.
5
5
  License-Expression: ISC
6
6
  Project-URL: Homepage, https://github.com/abdussamiakanda/gitlatex
@@ -261,7 +261,11 @@ An Overleaf-style review panel for leaving comments on your text.
261
261
  - **Shared through Git.** Each thread is a small JSON file in
262
262
  `.gitlatex/comments/` inside the project, so comments are pushed and pulled
263
263
  with your files. Each thread has its own file, so two people starting threads
264
- at the same time won't get merge conflicts.
264
+ at the same time won't get merge conflicts, and replies made on both sides of
265
+ a pull are combined automatically (the app registers a git merge driver for
266
+ these files, so this also works for `git pull` in a terminal).
267
+ See [docs/sync-and-conflicts.md](docs/sync-and-conflicts.md) for how push,
268
+ pull and conflicts are handled.
265
269
  - Comments **stay attached to their text** as you edit, find it again after a
266
270
  pull, and follow a file when you rename it. Deleting a file removes its
267
271
  comments.
@@ -233,7 +233,11 @@ An Overleaf-style review panel for leaving comments on your text.
233
233
  - **Shared through Git.** Each thread is a small JSON file in
234
234
  `.gitlatex/comments/` inside the project, so comments are pushed and pulled
235
235
  with your files. Each thread has its own file, so two people starting threads
236
- at the same time won't get merge conflicts.
236
+ at the same time won't get merge conflicts, and replies made on both sides of
237
+ a pull are combined automatically (the app registers a git merge driver for
238
+ these files, so this also works for `git pull` in a terminal).
239
+ See [docs/sync-and-conflicts.md](docs/sync-and-conflicts.md) for how push,
240
+ pull and conflicts are handled.
237
241
  - Comments **stay attached to their text** as you edit, find it again after a
238
242
  pull, and follow a file when you rename it. Deleting a file removes its
239
243
  comments.
@@ -0,0 +1,206 @@
1
+ # Syncing with coauthors: push, pull and conflicts
2
+
3
+ gitlatex projects are shared through Git, so two people working at once
4
+ eventually change the same thing. This page covers what the app does
5
+ automatically when that happens, what it reports when it can't, and how to fix
6
+ the rest by hand.
7
+
8
+ - **Push** commits everything and uploads it. If a coauthor pushed first, the
9
+ app pulls their commits in, puts yours on top, and pushes again.
10
+ - **Pull** downloads the remote's commits and rebases your unpushed commits on
11
+ top of them. Uncommitted edits are set aside and put back afterwards.
12
+ - **Review comments** replied to on both sides are combined, never conflicted.
13
+ - **A real conflict** (both of you changed the same lines of a `.tex` file) is
14
+ never left half-done: the app undoes the pull, puts the project back as it
15
+ was, and lists the files involved.
16
+
17
+ The code lives in `gitlatex/routes/git.py` (push and pull),
18
+ `gitlatex/services/comments.py` and `gitlatex/services/comments_merge.py`
19
+ (review comment threads). Tests are in `tests/test_sync.py`.
20
+
21
+ ## Two people, one remote
22
+
23
+ Alice commits but doesn't push. Meanwhile Bob pushes:
24
+
25
+ ```
26
+ remote: X ── B
27
+ Alice: X ── A (committed, not pushed)
28
+ ```
29
+
30
+ Alice's push can't simply go through: it would throw away Bob's commit, so Git
31
+ rejects it (`[rejected] (fetch first)`). Nothing is lost; Alice has to take
32
+ Bob's commit first.
33
+
34
+ ### What Push does
35
+
36
+ 1. Stages and commits everything (`git add -A`, then commit with your message).
37
+ 2. Pushes. If the remote rejects it because it has newer commits:
38
+ 3. Pulls with rebase (see below), so the history becomes `X ── B ── A`.
39
+ 4. Pushes again.
40
+
41
+ When the two of you changed different parts of the project, one click does
42
+ all of it. The toast says *"Your coauthors' newer changes were pulled in
43
+ first"*, and the editor reloads the files that changed.
44
+
45
+ If step 3 hits a real conflict, the push stops with **409** and a message like:
46
+
47
+ > Could not push: someone else pushed changes to the same lines first. Changed
48
+ > on both sides: main.tex. Your commit is saved here but not pushed yet.
49
+
50
+ Your commit stays in your local history, and the project is exactly as it was
51
+ before you pressed Push.
52
+
53
+ If someone pushes again between steps 3 and 4, the push stops with *"the
54
+ remote changed again while pushing. Try again."*
55
+
56
+ ### What Pull does
57
+
58
+ Pull runs `git pull --rebase --autostash`:
59
+
60
+ - **`--rebase`**: your unpushed commits go on top of the remote's, with no
61
+ "Merge branch …" commit every time two people work at once.
62
+ - **`--autostash`**: uncommitted edits are set aside before the pull and put
63
+ back afterwards.
64
+
65
+ A pull is always either finished or fully undone, never left half-merged:
66
+
67
+ | Situation | Result |
68
+ | --- | --- |
69
+ | Different lines changed | Pulled. Your commits sit on top of the remote's. |
70
+ | Same lines changed in **commits** | Rebase aborted. 409 listing the files. |
71
+ | Same lines changed by **uncommitted** edits | Pull undone and your edits restored. 409 listing the files. |
72
+ | Only build output conflicts (`main.pdf`, `main.synctex.gz`, `.aux`, …) | Your copy is kept and the pull finishes. The next compile rewrites it anyway. |
73
+ | Review comment threads changed on both sides | Merged automatically (see below). |
74
+
75
+ ## Review comments
76
+
77
+ Each review thread is one JSON file in `.gitlatex/comments/<id>.json`, and a
78
+ reply appends to that file's `messages` list. So if Alice and Bob both reply to
79
+ the same thread before syncing, both change the end of the same list, and a
80
+ plain Git merge always conflicts there.
81
+
82
+ ### The merge driver
83
+
84
+ `gitlatex/services/comments_merge.py` is a
85
+ [Git merge driver](https://git-scm.com/docs/gitattributes#_defining_a_custom_merge_driver):
86
+ Git runs it instead of its usual line-by-line merge whenever both sides changed
87
+ the same thread file. It merges the fields like this:
88
+
89
+ | Field | Rule |
90
+ | --- | --- |
91
+ | `messages` | Union of both sides by `id`, sorted by `date`; the opening message stays first. A message in the common ancestor that one side no longer has was deleted there, so it stays deleted. |
92
+ | `resolved`, `resolvedBy`, `resolvedAt` | Treated as one change. The side that changed it wins; if both did, the later `resolvedAt` wins. |
93
+ | `range`, `quote`, `file` | The side that changed it wins, otherwise ours. The editor finds the text again from `quote` after a pull anyway. |
94
+ | Anything that isn't valid JSON | The driver exits with 1 and Git falls back to a normal conflict. |
95
+
96
+ ### How it is registered
97
+
98
+ `comments.install_merge_driver()` runs whenever a project is opened, selected
99
+ or cloned, and before every pull. It writes two things, both inside the
100
+ project's own `.git` folder, so nothing new shows up in the project's files or
101
+ commits:
102
+
103
+ ```ini
104
+ # .git/config
105
+ [merge "gitlatex-comments"]
106
+ name = gitlatex review comment threads
107
+ driver = '/path/to/python' '/path/to/gitlatex/services/comments_merge.py' %O %A %B
108
+ ```
109
+
110
+ ```
111
+ # .git/info/attributes
112
+ .gitlatex/comments/*.json merge=gitlatex-comments
113
+ ```
114
+
115
+ A merge driver is a command on one machine, so it can't be shared through the
116
+ repository: each copy of the project sets up its own. Once registered, it also
117
+ works for `git pull` and `git merge` in a terminal. If the app is moved or
118
+ reinstalled, the next time a project is opened the command is updated.
119
+
120
+ ### Threads with conflict markers
121
+
122
+ A thread file can still end up with `<<<<<<<` markers, for example from a merge
123
+ made on a machine without the app. Such a file isn't valid JSON. It used to be
124
+ skipped without a word, so the thread just disappeared from the panel. Now the
125
+ panel shows a yellow card in its place, *"This thread has a merge conflict"*,
126
+ with the path of the file. Renaming the commented file leaves the conflicted
127
+ thread file untouched.
128
+
129
+ ## Fixing a conflict by hand
130
+
131
+ The app has no screen for resolving conflicts in `.tex` files yet. When it
132
+ reports one, open a terminal in the project folder:
133
+
134
+ ```sh
135
+ git pull --rebase # stops at the first conflict
136
+ git status # lists the files marked "both modified"
137
+ ```
138
+
139
+ In each file, choose what to keep between the markers, then delete the marker
140
+ lines:
141
+
142
+ ```latex
143
+ <<<<<<< HEAD
144
+ Bob's version of the paragraph.
145
+ =======
146
+ Alice's version of the paragraph.
147
+ >>>>>>> Alice's commit
148
+ ```
149
+
150
+ During a rebase, the `HEAD` side is the remote (Bob), and the other side is
151
+ your commit being replayed. Then:
152
+
153
+ ```sh
154
+ git add main.tex
155
+ git rebase --continue # repeat for each stopped commit
156
+ git push
157
+ ```
158
+
159
+ `git rebase --abort` goes back to where you started at any point.
160
+
161
+ **A conflicted comment thread** (only possible without the merge driver):
162
+ merge the two `messages` lists, keeping every message from both sides in date
163
+ order. Check the commas between entries and remove the marker lines. Take
164
+ `range` and `quote` from either side.
165
+
166
+ ```json
167
+ "messages": [
168
+ { "id": "a1…", "author": "Alice", "date": "2026-09-29T10:00:00", "text": "Is this right?" },
169
+ { "id": "b2…", "author": "Bob", "date": "2026-09-29T11:00:00", "text": "Yes" },
170
+ { "id": "c3…", "author": "Alice", "date": "2026-09-29T11:05:00", "text": "Also check eq. 3" }
171
+ ]
172
+ ```
173
+
174
+ ## What changed
175
+
176
+ Before these changes:
177
+
178
+ - **Push reported success when the push was rejected.** GitPython reports a
179
+ rejected push in its result instead of raising, and the result wasn't
180
+ checked. The commit never reached the remote, and nobody was told.
181
+ - **Pull failed when both sides had new commits**, with *"Need to specify how
182
+ to reconcile divergent branches"* (Git 2.27 and later, when `pull.rebase`
183
+ isn't set).
184
+ - **Uncommitted edits on the same lines** as incoming changes left conflict
185
+ markers inside the `.tex` file, while the pull reported success.
186
+ - **Replies on both sides of a comment thread** always conflicted, and the
187
+ thread then disappeared from the review panel.
188
+
189
+ | File | Change |
190
+ | --- | --- |
191
+ | `gitlatex/routes/git.py` | `_push()` detects rejected pushes. `_pull_rebase()` pulls with rebase, keeps local build output, and undoes anything that conflicts. Push retries after pulling. Conflicts return 409 with a `conflicts` list. |
192
+ | `gitlatex/services/comments_merge.py` | New: the merge driver for comment threads. |
193
+ | `gitlatex/services/comments.py` | `install_merge_driver()`. Conflicted thread files are listed as placeholders instead of being skipped. |
194
+ | `gitlatex/routes/repos.py`, `gitlatex/routes/workspace.py` | Register the merge driver when a project is opened, selected or cloned. |
195
+ | `web/src/storage/server.ts`, `web/src/state/actions.ts` | Push reports `pulled`. The editor reloads and says so. |
196
+ | `web/src/editor/review.ts`, `web/src/editor/review.css` | The merge-conflict card in the review panel. |
197
+ | `tests/test_sync.py` | New: unit tests for the merge driver, and two-clone tests of push and pull through the app. |
198
+
199
+ Run the tests with:
200
+
201
+ ```sh
202
+ python3 -m unittest discover tests
203
+ ```
204
+
205
+ The classic editor at `/classic` shows the new push and pull messages, but its
206
+ review panel doesn't have the merge-conflict card.
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '2.0.1'
22
- __version_tuple__ = version_tuple = (2, 0, 1)
21
+ __version__ = version = '2.1.0'
22
+ __version_tuple__ = version_tuple = (2, 1, 0)
23
23
 
24
- __commit_id__ = commit_id = 'ga0d651549'
24
+ __commit_id__ = commit_id = 'g71d35ed41'
@@ -14,6 +14,7 @@ blueprint below - nothing else in the app needs to know about it.
14
14
  snippets the user's own editor snippets
15
15
  review comment threads on lines of project files
16
16
  git push, pull, status, history and diffs
17
+ scm the Source control panel: staging, conflicts, branches, publish
17
18
  workspace the file API behind the editor UI in web/
18
19
  engine the in-browser TeX engine and its package shelf
19
20
 
@@ -30,6 +31,7 @@ from gitlatex.routes import (
30
31
  projectindex,
31
32
  repos,
32
33
  review,
34
+ scm,
33
35
  snippets,
34
36
  spell,
35
37
  synctex,
@@ -48,6 +50,7 @@ BLUEPRINTS = (
48
50
  snippets.bp,
49
51
  review.bp,
50
52
  git.bp,
53
+ scm.bp,
51
54
  workspace.bp,
52
55
  engine.bp,
53
56
  pages.bp,
@@ -1,7 +1,6 @@
1
1
  """Git operations: push, pull, status, history and diffs."""
2
2
 
3
3
  import datetime
4
- import re
5
4
  import os
6
5
 
7
6
  from flask import Blueprint, jsonify, request
@@ -11,6 +10,7 @@ from gitlatex.http import with_project_lock
11
10
  from gitlatex.services.git_backend import NULL_TREE, GitCommandError, Repo
12
11
  from gitlatex.services.gitrepo import _blob_text, _close_repo, _numstat, _open_repo
13
12
  from gitlatex.services.paths import resolve_repo_path
13
+ from gitlatex.services.sync import Conflict, clear_stale_build_outputs, pull_rebase, push_current
14
14
 
15
15
  bp = Blueprint("git", __name__)
16
16
 
@@ -41,9 +41,24 @@ def push():
41
41
  repo.index.commit(message)
42
42
  committed = True
43
43
  print("Commit:", message)
44
- repo.remotes.origin.push()
44
+ pulled = False
45
+ if not push_current(repo):
46
+ # Someone pushed first. Put our commits on top of theirs and
47
+ # try once more, so the common case needs no second click.
48
+ print("Push rejected; pulling first")
49
+ try:
50
+ pull_rebase(repo)
51
+ except Conflict as c:
52
+ return _conflict_response(c, committed,
53
+ "Could not push: someone else pushed changes to the same lines first.")
54
+ pulled = True
55
+ if not push_current(repo):
56
+ return jsonify(
57
+ error="Could not push: the remote changed again while pushing. Try again.",
58
+ committed=committed, pulled=True,
59
+ ), 409
45
60
  print("Pushed to origin")
46
- return jsonify(success=True, committed=committed)
61
+ return jsonify(success=True, committed=committed, pulled=pulled)
47
62
  finally:
48
63
  if getattr(repo, "close", None):
49
64
  try:
@@ -102,58 +117,16 @@ def _json_message():
102
117
  return message.strip() if isinstance(message, str) else ""
103
118
 
104
119
 
105
- # What TeX writes next to a document. Committed copies are common (people
106
- # commit main.pdf), and every compile rewrites them locally.
107
- _BUILD_OUTPUT = re.compile(
108
- r"\.(synctex\.gz|synctex|aux|log|out|toc|lof|lot|fls|fdb_latexmk|bcf|run\.xml|blg|bbl|nav|snm|vrb|xdv)$",
109
- re.IGNORECASE,
110
- )
111
-
112
-
113
- def _is_build_output(root, path):
114
- if _BUILD_OUTPUT.search(path):
115
- return True
116
- # A PDF is build output only when it is a document's own PDF (main.pdf next
117
- # to main.tex); figure PDFs are the user's files.
118
- if path.lower().endswith(".pdf"):
119
- return os.path.isfile(os.path.join(root, path[:-4] + ".tex"))
120
- return False
121
-
122
-
123
- def _clear_stale_build_outputs(repo):
124
- """Drop local build outputs that the incoming commits replace.
125
-
126
- Compiling rewrites main.pdf (PDFs embed a timestamp) and writes
127
- main.synctex.gz, so a project that commits them always looks modified, and
128
- `git pull` refuses to overwrite them. They are regenerated by the next
129
- compile anyway, so the remote's copies win. Only files the remote actually
130
- changes are touched; the user's own edits are left to --autostash.
131
- Returns the paths cleared.
132
- """
133
- try:
134
- if not repo.remotes or repo.head.is_detached or repo.active_branch.tracking_branch() is None:
135
- return []
136
- repo.git.fetch("--quiet")
137
- incoming = repo.git.diff("--name-only", "HEAD...@{u}").splitlines()
138
- except (GitCommandError, TypeError, ValueError):
139
- return []
140
- root = repo.working_tree_dir
141
- modified = set(repo.git.diff("--name-only").splitlines()) | set(repo.git.diff("--name-only", "--cached").splitlines())
142
- untracked = set(repo.untracked_files)
143
- cleared = []
144
- for path in incoming:
145
- if not _is_build_output(root, path):
146
- continue
147
- try:
148
- if path in modified:
149
- repo.git.checkout("HEAD", "--", path)
150
- cleared.append(path)
151
- elif path in untracked:
152
- os.remove(os.path.join(root, path))
153
- cleared.append(path)
154
- except (GitCommandError, OSError) as e:
155
- print("Could not clear", path, e)
156
- return cleared
120
+ def _conflict_response(conflict, committed, headline):
121
+ """409 for a pull that was undone because of conflicting edits."""
122
+ files = conflict.files
123
+ listed = ", ".join(files) if files else "some files"
124
+ kept = "Your commit is saved here but not pushed yet." if committed else "Nothing was changed on your side."
125
+ return jsonify(
126
+ error="%s Changed on both sides: %s. %s Resolve it in a terminal with `git pull --rebase`, "
127
+ "or agree with your coauthor which version to keep." % (headline, listed, kept),
128
+ conflicts=files, committed=committed,
129
+ ), 409
157
130
 
158
131
 
159
132
  @bp.route("/pull", methods=["POST"])
@@ -167,15 +140,11 @@ def pull():
167
140
  repo = Repo(state.current_repo_path)
168
141
  try:
169
142
  before = repo.head.commit.hexsha if repo.head.is_valid() else None
170
- cleared = _clear_stale_build_outputs(repo)
171
- # --autostash sets the user's own uncommitted edits aside and puts
172
- # them back after the merge; older gits don't support it for merges.
143
+ cleared = clear_stale_build_outputs(repo)
173
144
  try:
174
- output = repo.git.pull("--autostash")
175
- except GitCommandError as e:
176
- if "--autostash" not in str(e) and "autostash" not in str(e).lower():
177
- raise
178
- output = repo.git.pull()
145
+ output = pull_rebase(repo)
146
+ except Conflict as c:
147
+ return _conflict_response(c, False, "Could not pull: you and the remote changed the same lines.")
179
148
  after = repo.head.commit.hexsha if repo.head.is_valid() else None
180
149
  print("Pulled from origin" + (" (replaced local build output: %s)" % ", ".join(cleared) if cleared else ""))
181
150
  return jsonify(success=True, output=output, changed=before != after, cleared=cleared)
@@ -10,7 +10,7 @@ import sys
10
10
  from flask import Blueprint, jsonify
11
11
 
12
12
  from gitlatex import state
13
- from gitlatex.services import paths
13
+ from gitlatex.services import comments, paths
14
14
  from gitlatex.services.git_backend import Repo, git_rmtree
15
15
  from gitlatex.http import _json
16
16
  from gitlatex.services.paths import count_files_in_dir, parse_owner_from_remote_url
@@ -194,6 +194,8 @@ def select_repo():
194
194
  state.current_repo_path = repo_path
195
195
  git_dir = os.path.join(repo_path, ".git")
196
196
  has_git = os.path.isdir(git_dir)
197
+ if has_git:
198
+ comments.install_merge_driver(repo_path)
197
199
  print("Selected repo:", name)
198
200
  return jsonify(success=True, hasGit=has_git)
199
201
 
@@ -212,6 +214,7 @@ def clone_repo():
212
214
  try:
213
215
  Repo.clone_from(repo_url, repo_path)
214
216
  print("Cloned", repo_name)
217
+ comments.install_merge_driver(repo_path)
215
218
  state.current_repo_path = repo_path
216
219
  return jsonify(success=True)
217
220
  except Exception as e:
@@ -0,0 +1,221 @@
1
+ """Source control API for the editor's Source control panel (services/scm.py).
2
+
3
+ GET /api/scm/status branch, upstream, ahead/behind, staged/changes/conflicts
4
+ POST /api/scm/stage {paths?} stage files (all when omitted)
5
+ POST /api/scm/unstage {paths?}
6
+ POST /api/scm/discard {paths} drop working-tree changes
7
+ POST /api/scm/resolve {path, side: current|incoming}
8
+ POST /api/scm/mark-resolved {path}
9
+ POST /api/scm/abort abort the merge / rebase in progress
10
+ POST /api/scm/commit {message}
11
+ POST /api/scm/pull | push | sync | fetch
12
+ GET /api/scm/branches POST /api/scm/checkout {name, create?}
13
+ GET /api/scm/publish-options POST /api/scm/publish {url? | name?, private?}
14
+ GET /api/scm/diff?path=&staged=1
15
+
16
+ Pull and push rebase like /pull and /push (services/sync.py), but a real
17
+ conflict is left in the files for the editor to resolve. Everything that
18
+ changes files holds state.project_lock, like the editor's writes.
19
+ """
20
+
21
+ import functools
22
+
23
+ from flask import Blueprint, jsonify, request
24
+
25
+ from gitlatex import state
26
+ from gitlatex.http import _json, with_project_lock
27
+ from gitlatex.services import scm
28
+ from gitlatex.services.git_backend import GitCommandError, Repo
29
+ from gitlatex.services.gitrepo import _close_repo
30
+
31
+ bp = Blueprint("scm", __name__)
32
+
33
+
34
+ def _git_error(e):
35
+ """The useful part of a failed git command: its stderr, without GitPython's framing."""
36
+ text = (getattr(e, "stderr", "") or str(e)).strip()
37
+ if text.startswith("stderr:"):
38
+ text = text[len("stderr:"):]
39
+ return text.strip().strip("'").strip() or str(e)
40
+
41
+
42
+ def with_repo(view):
43
+ """Open the current project's repository, pass it in, and turn failures into JSON errors."""
44
+
45
+ @functools.wraps(view)
46
+ def wrapper(*args, **kwargs):
47
+ if not state.current_repo_path:
48
+ return jsonify(error="No project open"), 400
49
+ if Repo is None:
50
+ return jsonify(error="GitPython not installed"), 500
51
+ try:
52
+ repo = Repo(state.current_repo_path)
53
+ except Exception:
54
+ return jsonify(error="This project is not a Git repository."), 400
55
+ try:
56
+ return view(repo, *args, **kwargs)
57
+ except ValueError as e:
58
+ return jsonify(error=str(e)), 400
59
+ except GitCommandError as e:
60
+ return jsonify(error=_git_error(e)), 500
61
+ finally:
62
+ _close_repo(repo)
63
+
64
+ return wrapper
65
+
66
+
67
+ def _paths():
68
+ paths = _json().get("paths")
69
+ return [p for p in paths if isinstance(p, str) and p] if isinstance(paths, list) else None
70
+
71
+
72
+ def _ok(repo, **extra):
73
+ """Every change returns the fresh status, so the panel redraws from one response."""
74
+ return jsonify(success=True, status=scm.status(repo), **extra)
75
+
76
+
77
+ @bp.route("/api/scm/status")
78
+ @with_repo
79
+ def get_status(repo):
80
+ return jsonify(scm.status(repo))
81
+
82
+
83
+ @bp.route("/api/scm/stage", methods=["POST"])
84
+ @with_project_lock
85
+ @with_repo
86
+ def stage(repo):
87
+ scm.stage(repo, _paths())
88
+ return _ok(repo)
89
+
90
+
91
+ @bp.route("/api/scm/unstage", methods=["POST"])
92
+ @with_project_lock
93
+ @with_repo
94
+ def unstage(repo):
95
+ scm.unstage(repo, _paths(), has_commits=scm.status(repo)["hasCommits"])
96
+ return _ok(repo)
97
+
98
+
99
+ @bp.route("/api/scm/discard", methods=["POST"])
100
+ @with_project_lock
101
+ @with_repo
102
+ def discard(repo):
103
+ paths = _paths()
104
+ if not paths:
105
+ raise ValueError("No files given")
106
+ scm.discard(repo, paths)
107
+ return _ok(repo)
108
+
109
+
110
+ @bp.route("/api/scm/resolve", methods=["POST"])
111
+ @with_project_lock
112
+ @with_repo
113
+ def resolve(repo):
114
+ data = _json()
115
+ side = data.get("side")
116
+ if side not in ("current", "incoming") or not data.get("path"):
117
+ raise ValueError("Give a path and side: current or incoming")
118
+ scm.take_side(repo, data["path"], side)
119
+ return _ok(repo)
120
+
121
+
122
+ @bp.route("/api/scm/mark-resolved", methods=["POST"])
123
+ @with_project_lock
124
+ @with_repo
125
+ def mark_resolved(repo):
126
+ path = _json().get("path")
127
+ if not path:
128
+ raise ValueError("No path given")
129
+ scm.mark_resolved(repo, path)
130
+ return _ok(repo)
131
+
132
+
133
+ @bp.route("/api/scm/abort", methods=["POST"])
134
+ @with_project_lock
135
+ @with_repo
136
+ def abort(repo):
137
+ scm.abort(repo)
138
+ return _ok(repo)
139
+
140
+
141
+ @bp.route("/api/scm/commit", methods=["POST"])
142
+ @with_project_lock
143
+ @with_repo
144
+ def commit(repo):
145
+ return _ok(repo, **scm.commit(repo, (_json().get("message") or "").strip()))
146
+
147
+
148
+ @bp.route("/api/scm/pull", methods=["POST"])
149
+ @with_project_lock
150
+ @with_repo
151
+ def pull(repo):
152
+ return _ok(repo, **scm.pull(repo))
153
+
154
+
155
+ @bp.route("/api/scm/push", methods=["POST"])
156
+ @with_project_lock
157
+ @with_repo
158
+ def push(repo):
159
+ return _ok(repo, **scm.push(repo))
160
+
161
+
162
+ @bp.route("/api/scm/sync", methods=["POST"])
163
+ @with_project_lock
164
+ @with_repo
165
+ def sync(repo):
166
+ """Pull, then push if the pull left nothing to resolve (VS Code's Sync Changes)."""
167
+ result = scm.pull(repo)
168
+ if not result["conflicts"] and scm.status(repo)["ahead"]:
169
+ pushed = scm.push(repo)
170
+ result = {**pushed, "cleared": result["cleared"] + pushed["cleared"]}
171
+ return _ok(repo, **result)
172
+
173
+
174
+ @bp.route("/api/scm/fetch", methods=["POST"])
175
+ @with_repo
176
+ def fetch(repo):
177
+ if repo.remotes:
178
+ repo.git.fetch("--all", "--prune")
179
+ return _ok(repo)
180
+
181
+
182
+ @bp.route("/api/scm/branches")
183
+ @with_repo
184
+ def branches(repo):
185
+ return jsonify(scm.branches(repo))
186
+
187
+
188
+ @bp.route("/api/scm/checkout", methods=["POST"])
189
+ @with_project_lock
190
+ @with_repo
191
+ def checkout(repo):
192
+ data = _json()
193
+ name = (data.get("name") or "").strip()
194
+ if not name:
195
+ raise ValueError("No branch name given")
196
+ scm.checkout(repo, name, create=bool(data.get("create")))
197
+ return _ok(repo)
198
+
199
+
200
+ @bp.route("/api/scm/publish-options")
201
+ def publish_options():
202
+ return jsonify(gh=scm.gh_ready())
203
+
204
+
205
+ @bp.route("/api/scm/publish", methods=["POST"])
206
+ @with_project_lock
207
+ @with_repo
208
+ def publish(repo):
209
+ data = _json()
210
+ url = (data.get("url") or "").strip() or None
211
+ output = scm.publish(repo, url=url, name=data.get("name"), private=data.get("private", True) is not False)
212
+ return _ok(repo, output=output)
213
+
214
+
215
+ @bp.route("/api/scm/diff")
216
+ @with_repo
217
+ def diff(repo):
218
+ path = (request.args.get("path") or "").replace("\\", "/")
219
+ if not path:
220
+ raise ValueError("No path given")
221
+ return jsonify(scm.diff(repo, path, staged=request.args.get("staged") in ("1", "true")))
@@ -115,6 +115,8 @@ def open_project():
115
115
  state.current_repo_path = full
116
116
  files, folders = _read_project(full)
117
117
  has_git = os.path.isdir(os.path.join(full, ".git"))
118
+ if has_git:
119
+ comments.install_merge_driver(full)
118
120
  print("Opened project:", os.path.basename(full))
119
121
  return jsonify(name=os.path.basename(full), hasGit=has_git, files=files, folders=folders)
120
122