calkit-python 0.35.5__tar.gz → 0.35.6__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 (285) hide show
  1. {calkit_python-0.35.5 → calkit_python-0.35.6}/PKG-INFO +1 -1
  2. calkit_python-0.35.6/calkit/git.py +297 -0
  3. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/pipeline.py +5 -15
  4. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/test_git.py +59 -10
  5. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/test_pipeline.py +58 -5
  6. calkit_python-0.35.5/calkit/git.py +0 -197
  7. {calkit_python-0.35.5 → calkit_python-0.35.6}/.gitignore +0 -0
  8. {calkit_python-0.35.5 → calkit_python-0.35.6}/.pre-commit-config.yaml +0 -0
  9. {calkit_python-0.35.5 → calkit_python-0.35.6}/.prettierignore +0 -0
  10. {calkit_python-0.35.5 → calkit_python-0.35.6}/.python-version +0 -0
  11. {calkit_python-0.35.5 → calkit_python-0.35.6}/.yarnrc.yml +0 -0
  12. {calkit_python-0.35.5 → calkit_python-0.35.6}/CITATION.cff +0 -0
  13. {calkit_python-0.35.5 → calkit_python-0.35.6}/CODE_OF_CONDUCT.md +0 -0
  14. {calkit_python-0.35.5 → calkit_python-0.35.6}/CONTRIBUTING.md +0 -0
  15. {calkit_python-0.35.5 → calkit_python-0.35.6}/LICENSE +0 -0
  16. {calkit_python-0.35.5 → calkit_python-0.35.6}/Makefile +0 -0
  17. {calkit_python-0.35.5 → calkit_python-0.35.6}/README.md +0 -0
  18. {calkit_python-0.35.5 → calkit_python-0.35.6}/babel.config.js +0 -0
  19. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/__init__.py +0 -0
  20. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/__main__.py +0 -0
  21. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/calc.py +0 -0
  22. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/check.py +0 -0
  23. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/cli/__init__.py +0 -0
  24. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/cli/check.py +0 -0
  25. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/cli/cloud.py +0 -0
  26. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/cli/config.py +0 -0
  27. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/cli/core.py +0 -0
  28. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/cli/describe.py +0 -0
  29. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/cli/import_.py +0 -0
  30. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/cli/latex.py +0 -0
  31. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/cli/list.py +0 -0
  32. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/cli/main/__init__.py +0 -0
  33. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/cli/main/core.py +0 -0
  34. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/cli/main/xr.py +0 -0
  35. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/cli/new.py +0 -0
  36. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/cli/notebooks.py +0 -0
  37. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/cli/office.py +0 -0
  38. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/cli/overleaf.py +0 -0
  39. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/cli/slurm.py +0 -0
  40. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/cli/update.py +0 -0
  41. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/cloud.py +0 -0
  42. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/conda.py +0 -0
  43. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/config.py +0 -0
  44. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/core.py +0 -0
  45. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/datasets.py +0 -0
  46. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/detect.py +0 -0
  47. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/docker.py +0 -0
  48. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/dvc.py +0 -0
  49. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/environments.py +0 -0
  50. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/fs.py +0 -0
  51. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/github.py +0 -0
  52. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/gui.py +0 -0
  53. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/invenio.py +0 -0
  54. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/julia.py +0 -0
  55. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/jupyter.py +0 -0
  56. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/jupyterlab/__init__.py +0 -0
  57. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/jupyterlab/routes.py +0 -0
  58. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/labextension/package.json +0 -0
  59. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/labextension/schemas/calkit/package.json.orig +0 -0
  60. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/labextension/schemas/calkit/plugin.json +0 -0
  61. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/labextension/static/502.9a2c5772a15466e923ef.js +0 -0
  62. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/labextension/static/695.2c41003a452d43d2b358.js +0 -0
  63. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/labextension/static/867.a42a046aa5108f54f8fb.js +0 -0
  64. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/labextension/static/909.651be47ca47390b78a92.js +0 -0
  65. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/labextension/static/946.050af2abf7845cfbdbd2.js +0 -0
  66. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/labextension/static/946.050af2abf7845cfbdbd2.js.LICENSE.txt +0 -0
  67. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/labextension/static/b2f1c3efe70cb539d121.png +0 -0
  68. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/labextension/static/remoteEntry.c091821b3d7f2d287a67.js +0 -0
  69. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/labextension/static/style.js +0 -0
  70. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/labextension/static/third-party-licenses.json +0 -0
  71. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/licenses.py +0 -0
  72. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/magics.py +0 -0
  73. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/matlab.py +0 -0
  74. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/models/__init__.py +0 -0
  75. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/models/core.py +0 -0
  76. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/models/io.py +0 -0
  77. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/models/iteration.py +0 -0
  78. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/models/pipeline.py +0 -0
  79. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/notebooks.py +0 -0
  80. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/office.py +0 -0
  81. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/ops.py +0 -0
  82. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/overleaf.py +0 -0
  83. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/releases.py +0 -0
  84. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/server.py +0 -0
  85. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/templates/__init__.py +0 -0
  86. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/templates/core.py +0 -0
  87. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/templates/latex/__init__.py +0 -0
  88. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/templates/latex/article/paper.tex +0 -0
  89. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/templates/latex/core.py +0 -0
  90. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/templates/latex/jfm/jfm.bst +0 -0
  91. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/templates/latex/jfm/jfm.cls +0 -0
  92. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/templates/latex/jfm/lineno-FLM.sty +0 -0
  93. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/templates/latex/jfm/paper.tex +0 -0
  94. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/templates/latex/jfm/upmath.sty +0 -0
  95. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/__init__.py +0 -0
  96. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/cli/__init__.py +0 -0
  97. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/cli/main/__init__.py +0 -0
  98. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/cli/main/test_core.py +0 -0
  99. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/cli/main/test_xr.py +0 -0
  100. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/cli/test_check.py +0 -0
  101. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/cli/test_config.py +0 -0
  102. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/cli/test_import.py +0 -0
  103. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/cli/test_latex.py +0 -0
  104. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/cli/test_list.py +0 -0
  105. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/cli/test_new.py +0 -0
  106. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/cli/test_notebooks.py +0 -0
  107. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/cli/test_overleaf.py +0 -0
  108. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/jupyterlab/__init__.py +0 -0
  109. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/jupyterlab/test_routes.py +0 -0
  110. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/models/__init__.py +0 -0
  111. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/models/test_iteration.py +0 -0
  112. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/models/test_pipeline.py +0 -0
  113. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/test_calc.py +0 -0
  114. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/test_check.py +0 -0
  115. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/test_conda.py +0 -0
  116. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/test_core.py +0 -0
  117. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/test_detect.py +0 -0
  118. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/test_docker.py +0 -0
  119. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/test_dvc.py +0 -0
  120. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/test_environments.py +0 -0
  121. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/test_fs.py +0 -0
  122. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/test_invenio.py +0 -0
  123. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/test_julia.py +0 -0
  124. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/test_jupyter.py +0 -0
  125. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/test_magics.py +0 -0
  126. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/test_matlab.py +0 -0
  127. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/test_notebooks.py +0 -0
  128. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/test_releases.py +0 -0
  129. {calkit_python-0.35.5 → calkit_python-0.35.6}/calkit/tests/test_templates.py +0 -0
  130. {calkit_python-0.35.5 → calkit_python-0.35.6}/conftest.py +0 -0
  131. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/CNAME +0 -0
  132. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/apps.md +0 -0
  133. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/calculations.md +0 -0
  134. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/calkit-yaml.md +0 -0
  135. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/cli-reference.md +0 -0
  136. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/cloud-integration.md +0 -0
  137. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/datasets.md +0 -0
  138. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/dependencies.md +0 -0
  139. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/environments.md +0 -0
  140. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/examples.md +0 -0
  141. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/governance.md +0 -0
  142. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/help.md +0 -0
  143. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/img/c-to-the-k-white.svg +0 -0
  144. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/img/calkit-fragmentation-compendium.png +0 -0
  145. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/img/calkit-no-bg.png +0 -0
  146. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/img/connect-zenodo.png +0 -0
  147. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/img/jupyterlab/all-green.png +0 -0
  148. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/img/jupyterlab/collect-data-stale.png +0 -0
  149. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/img/jupyterlab/new-env.png +0 -0
  150. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/img/jupyterlab/new-notebook.png +0 -0
  151. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/img/jupyterlab/pipeline-badge.png +0 -0
  152. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/img/jupyterlab-params.png +0 -0
  153. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/img/plos-osi-code-2024-03.png +0 -0
  154. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/img/vscode-nb-params.png +0 -0
  155. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/index.md +0 -0
  156. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/installation.md +0 -0
  157. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/jupyterlab.md +0 -0
  158. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/local-server.md +0 -0
  159. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/notebooks.md +0 -0
  160. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/overleaf.md +0 -0
  161. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/pipeline/index.md +0 -0
  162. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/pipeline/manual-steps.md +0 -0
  163. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/pipeline/running-and-logging.md +0 -0
  164. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/pipeline/slurm.md +0 -0
  165. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/quickstart.md +0 -0
  166. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/references.md +0 -0
  167. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/releases.md +0 -0
  168. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/reproducibility.md +0 -0
  169. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/adding-latex-pub-docker.md +0 -0
  170. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/conda-envs.md +0 -0
  171. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/existing-project.md +0 -0
  172. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/first-project.md +0 -0
  173. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/github-actions.md +0 -0
  174. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/actions-repo-secrets.png +0 -0
  175. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/latex-codespaces/building-codespace.png +0 -0
  176. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/latex-codespaces/codespaces-secrets-2.png +0 -0
  177. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/latex-codespaces/editor-split.png +0 -0
  178. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/latex-codespaces/go-to-linked-code.png +0 -0
  179. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/latex-codespaces/issue-from-selection.png +0 -0
  180. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/latex-codespaces/new-project.png +0 -0
  181. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/latex-codespaces/new-pub-2.png +0 -0
  182. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/latex-codespaces/new-token.png +0 -0
  183. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/latex-codespaces/paper.tex.png +0 -0
  184. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/latex-codespaces/project-home-3.png +0 -0
  185. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/latex-codespaces/push.png +0 -0
  186. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/latex-codespaces/stage.png +0 -0
  187. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/office/anakin-excel.jpg +0 -0
  188. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/office/chart-more-rows.png +0 -0
  189. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/office/create-project.png +0 -0
  190. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/office/elsevier-research-data-guidelines.png +0 -0
  191. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/office/excel-chart.png +0 -0
  192. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/office/excel-data.png +0 -0
  193. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/office/insert-link-to-file.png +0 -0
  194. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/office/needs-clone.png +0 -0
  195. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/office/new-stage.png +0 -0
  196. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/office/phd-comics-version-control.webp +0 -0
  197. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/office/pipeline-out-of-date.png +0 -0
  198. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/office/status-more-rows.png +0 -0
  199. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/office/uncommitted-changes.png +0 -0
  200. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/office/untracked-data.png +0 -0
  201. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/office/updated-publication.png +0 -0
  202. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/office/word-to-pdf-stage-2.png +0 -0
  203. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/office/workflow-page.png +0 -0
  204. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/openfoam/clone.png +0 -0
  205. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/openfoam/create-project.png +0 -0
  206. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/openfoam/datasets-page.png +0 -0
  207. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/openfoam/figure-on-website-updated.png +0 -0
  208. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/openfoam/figure-on-website.png +0 -0
  209. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/openfoam/new-token.png +0 -0
  210. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/openfoam/reclone.png +0 -0
  211. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/openfoam/status-after-import-dataset.png +0 -0
  212. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/quick-actions.png +0 -0
  213. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/img/run-proc.png +0 -0
  214. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/index.md +0 -0
  215. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/jupyterlab.md +0 -0
  216. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/latex-codespaces.md +0 -0
  217. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/matlab.md +0 -0
  218. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/notebook-pipeline.md +0 -0
  219. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/office.md +0 -0
  220. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/openfoam.md +0 -0
  221. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/tutorials/procedures.md +0 -0
  222. {calkit_python-0.35.5 → calkit_python-0.35.6}/docs/version-control.md +0 -0
  223. {calkit_python-0.35.5 → calkit_python-0.35.6}/install.json +0 -0
  224. {calkit_python-0.35.5 → calkit_python-0.35.6}/jest.config.js +0 -0
  225. {calkit_python-0.35.5 → calkit_python-0.35.6}/jupyter-config/server-config/calkit.json +0 -0
  226. {calkit_python-0.35.5 → calkit_python-0.35.6}/mkdocs.yml +0 -0
  227. {calkit_python-0.35.5 → calkit_python-0.35.6}/package.json +0 -0
  228. {calkit_python-0.35.5 → calkit_python-0.35.6}/pyproject.toml +0 -0
  229. {calkit_python-0.35.5 → calkit_python-0.35.6}/schema/plugin.json +0 -0
  230. {calkit_python-0.35.5 → calkit_python-0.35.6}/scripts/generate-cli-reference.py +0 -0
  231. {calkit_python-0.35.5 → calkit_python-0.35.6}/scripts/install.ps1 +0 -0
  232. {calkit_python-0.35.5 → calkit_python-0.35.6}/scripts/install.sh +0 -0
  233. {calkit_python-0.35.5 → calkit_python-0.35.6}/scripts/make-calk9.sh +0 -0
  234. {calkit_python-0.35.5 → calkit_python-0.35.6}/scripts/sync-docs.py +0 -0
  235. {calkit_python-0.35.5 → calkit_python-0.35.6}/src/__tests__/useQueries.spec.ts +0 -0
  236. {calkit_python-0.35.5 → calkit_python-0.35.6}/src/calkit-config.ts +0 -0
  237. {calkit_python-0.35.5 → calkit_python-0.35.6}/src/cell-output-marker.ts +0 -0
  238. {calkit_python-0.35.5 → calkit_python-0.35.6}/src/components/commit-dialog.tsx +0 -0
  239. {calkit_python-0.35.5 → calkit_python-0.35.6}/src/components/environment-editor.tsx +0 -0
  240. {calkit_python-0.35.5 → calkit_python-0.35.6}/src/components/notebook-registration.tsx +0 -0
  241. {calkit_python-0.35.5 → calkit_python-0.35.6}/src/components/notebook-toolbar.tsx +0 -0
  242. {calkit_python-0.35.5 → calkit_python-0.35.6}/src/components/pipeline-status-bar.tsx +0 -0
  243. {calkit_python-0.35.5 → calkit_python-0.35.6}/src/components/project-info-editor.tsx +0 -0
  244. {calkit_python-0.35.5 → calkit_python-0.35.6}/src/components/sidebar-settings.tsx +0 -0
  245. {calkit_python-0.35.5 → calkit_python-0.35.6}/src/components/sidebar.tsx +0 -0
  246. {calkit_python-0.35.5 → calkit_python-0.35.6}/src/components/stage-editor.tsx +0 -0
  247. {calkit_python-0.35.5 → calkit_python-0.35.6}/src/feature-flags.ts +0 -0
  248. {calkit_python-0.35.5 → calkit_python-0.35.6}/src/file-browser-menu.ts +0 -0
  249. {calkit_python-0.35.5 → calkit_python-0.35.6}/src/hooks/__tests__/useQueries.test.tsx +0 -0
  250. {calkit_python-0.35.5 → calkit_python-0.35.6}/src/hooks/useQueries.ts +0 -0
  251. {calkit_python-0.35.5 → calkit_python-0.35.6}/src/icons.ts +0 -0
  252. {calkit_python-0.35.5 → calkit_python-0.35.6}/src/index.ts +0 -0
  253. {calkit_python-0.35.5 → calkit_python-0.35.6}/src/io-tracker.ts +0 -0
  254. {calkit_python-0.35.5 → calkit_python-0.35.6}/src/pipeline-state.ts +0 -0
  255. {calkit_python-0.35.5 → calkit_python-0.35.6}/src/queryClient.ts +0 -0
  256. {calkit_python-0.35.5 → calkit_python-0.35.6}/src/request.ts +0 -0
  257. {calkit_python-0.35.5 → calkit_python-0.35.6}/src/shims-mainmenu.d.ts +0 -0
  258. {calkit_python-0.35.5 → calkit_python-0.35.6}/style/base.css +0 -0
  259. {calkit_python-0.35.5 → calkit_python-0.35.6}/style/cell-output-marker.css +0 -0
  260. {calkit_python-0.35.5 → calkit_python-0.35.6}/style/environment-editor.css +0 -0
  261. {calkit_python-0.35.5 → calkit_python-0.35.6}/style/environment-selector.css +0 -0
  262. {calkit_python-0.35.5 → calkit_python-0.35.6}/style/img/calkit-no-bg.png +0 -0
  263. {calkit_python-0.35.5 → calkit_python-0.35.6}/style/index.css +0 -0
  264. {calkit_python-0.35.5 → calkit_python-0.35.6}/style/index.js +0 -0
  265. {calkit_python-0.35.5 → calkit_python-0.35.6}/style/notebook-toolbar.css +0 -0
  266. {calkit_python-0.35.5 → calkit_python-0.35.6}/style/pipeline-status-bar.css +0 -0
  267. {calkit_python-0.35.5 → calkit_python-0.35.6}/style/sidebar.css +0 -0
  268. {calkit_python-0.35.5 → calkit_python-0.35.6}/test/dvc-md5-dir/osx-arm64.txt +0 -0
  269. {calkit_python-0.35.5 → calkit_python-0.35.6}/test/nb-julia.ipynb +0 -0
  270. {calkit_python-0.35.5 → calkit_python-0.35.6}/test/nb-params.ipynb +0 -0
  271. {calkit_python-0.35.5 → calkit_python-0.35.6}/test/nb-subdir.ipynb +0 -0
  272. {calkit_python-0.35.5 → calkit_python-0.35.6}/test/pipeline.ipynb +0 -0
  273. {calkit_python-0.35.5 → calkit_python-0.35.6}/test/script.py +0 -0
  274. {calkit_python-0.35.5 → calkit_python-0.35.6}/test/test-log.log +0 -0
  275. {calkit_python-0.35.5 → calkit_python-0.35.6}/tsconfig.json +0 -0
  276. {calkit_python-0.35.5 → calkit_python-0.35.6}/tsconfig.test.json +0 -0
  277. {calkit_python-0.35.5 → calkit_python-0.35.6}/ui-tests/.gitignore +0 -0
  278. {calkit_python-0.35.5 → calkit_python-0.35.6}/ui-tests/README.md +0 -0
  279. {calkit_python-0.35.5 → calkit_python-0.35.6}/ui-tests/jupyter_server_test_config.py +0 -0
  280. {calkit_python-0.35.5 → calkit_python-0.35.6}/ui-tests/package.json +0 -0
  281. {calkit_python-0.35.5 → calkit_python-0.35.6}/ui-tests/playwright.config.js +0 -0
  282. {calkit_python-0.35.5 → calkit_python-0.35.6}/ui-tests/tests/calkit.spec.ts +0 -0
  283. {calkit_python-0.35.5 → calkit_python-0.35.6}/ui-tests/yarn.lock +0 -0
  284. {calkit_python-0.35.5 → calkit_python-0.35.6}/uv.lock +0 -0
  285. {calkit_python-0.35.5 → calkit_python-0.35.6}/yarn.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: calkit-python
3
- Version: 0.35.5
3
+ Version: 0.35.6
4
4
  Summary: Reproducibility simplified.
5
5
  Project-URL: Homepage, https://calkit.org
6
6
  Project-URL: Issues, https://github.com/calkit/calkit/issues
@@ -0,0 +1,297 @@
1
+ """Git-related functionality."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from os import PathLike
7
+ from pathlib import Path
8
+
9
+ import git
10
+
11
+
12
+ def get_staged_files(
13
+ path: str | None = None, repo: git.Repo | None = None
14
+ ) -> list[str]:
15
+ """Get a list of staged files for the repo at ``path`` or the provided
16
+ repo.
17
+ """
18
+ if repo is None:
19
+ repo = git.Repo(path)
20
+ cmd = ["--staged", "--name-only"]
21
+ if path is not None:
22
+ cmd.append(path)
23
+ diff = repo.git.diff(cmd)
24
+ paths = diff.split("\n")
25
+ return [p for p in paths if p]
26
+
27
+
28
+ def get_changed_files(
29
+ path: str | None = None, repo: git.Repo | None = None
30
+ ) -> list[str]:
31
+ """Get a list of files that have been changed but not staged."""
32
+ if repo is None:
33
+ repo = git.Repo(path)
34
+ return [
35
+ item.a_path
36
+ for item in repo.index.diff(None)
37
+ if item.a_path is not None
38
+ ]
39
+
40
+
41
+ def get_untracked_files(
42
+ path: str | None = None, repo: git.Repo | None = None
43
+ ) -> list[str]:
44
+ """Get a list of untracked files."""
45
+ if repo is None:
46
+ repo = git.Repo(path)
47
+ return repo.untracked_files
48
+
49
+
50
+ def get_staged_files_with_status(
51
+ path: str | None = None, repo: git.Repo | None = None
52
+ ) -> list[dict]:
53
+ if repo is None:
54
+ repo = git.Repo(path)
55
+ cmd = ["--staged", "--name-status"]
56
+ if path is not None:
57
+ cmd.append(path)
58
+ diff = repo.git.diff(cmd)
59
+ paths = diff.split("\n")
60
+ res = []
61
+ for pathi in paths:
62
+ # Make sure line is not empty, e.g., a trailing newline
63
+ if pathi:
64
+ status, p = pathi.split("\t")
65
+ res.append({"status": status, "path": p})
66
+ return res
67
+
68
+
69
+ def ls_files(repo: git.Repo, *args, **kwargs) -> list[str]:
70
+ """Get a list of all files tracked by git."""
71
+ output = repo.git.ls_files(*args, **kwargs)
72
+ return [f for f in output.split("\n") if f]
73
+
74
+
75
+ def _resolve_repo_and_ignore_path(
76
+ repo: git.Repo, path: str | PathLike
77
+ ) -> tuple[git.Repo, str]:
78
+ """Resolve which repo should own ignore rules for ``path``."""
79
+ # Normalize target path to absolute from the current repo root.
80
+ repo_root = Path(repo.working_dir).resolve()
81
+ path_obj = Path(path)
82
+ if path_obj.is_absolute():
83
+ abs_path = path_obj.resolve()
84
+ else:
85
+ abs_path = (repo_root / path_obj).resolve()
86
+ # If the path is inside a submodule, use that repo and relative path.
87
+ for submodule in repo.submodules:
88
+ submodule_root = (repo_root / submodule.path).resolve()
89
+ if abs_path == submodule_root:
90
+ continue
91
+ if abs_path.is_relative_to(submodule_root):
92
+ sub_repo = submodule.module()
93
+ rel_path = abs_path.relative_to(submodule_root).as_posix()
94
+ return sub_repo, rel_path
95
+ # Fall back to a repo-relative path when possible.
96
+ try:
97
+ rel_path = abs_path.relative_to(repo_root).as_posix()
98
+ except ValueError:
99
+ rel_path = path_obj.as_posix()
100
+ return repo, rel_path
101
+
102
+
103
+ def _get_matching_gitignore_details(
104
+ repo: git.Repo, path: str
105
+ ) -> tuple[Path | None, str | None]:
106
+ """Return the repo-local gitignore file and pattern matching ``path``."""
107
+ try:
108
+ check_ignore = repo.git.check_ignore("-v", "--", path)
109
+ except git.GitCommandError:
110
+ return None, None
111
+ line = check_ignore.splitlines()[0]
112
+ try:
113
+ source_info, _ = line.split("\t", 1)
114
+ source_path, _, pattern = source_info.rsplit(":", 2)
115
+ except ValueError:
116
+ return None, None
117
+ if not source_path.endswith(".gitignore"):
118
+ return None, pattern
119
+ gitignore_path = (Path(repo.working_dir) / source_path).resolve()
120
+ try:
121
+ gitignore_path.relative_to(Path(repo.working_dir).resolve())
122
+ except ValueError:
123
+ return None, pattern
124
+ return gitignore_path, pattern
125
+
126
+
127
+ def ensure_path_is_ignored(
128
+ repo: git.Repo, path: str | PathLike
129
+ ) -> None | bool:
130
+ """Ensure that the given path is ignored by Git.
131
+
132
+ Returns True if ``.gitignore`` was modified.
133
+ """
134
+ # Resolve whether the ignore rule belongs to this repo or a submodule.
135
+ target_repo, target_path = _resolve_repo_and_ignore_path(repo, path)
136
+ # No-op if Git already ignores this path.
137
+ if target_repo.ignored(target_path):
138
+ return
139
+ # Read gitignore first to check if the path is already ignored
140
+ # If not, we don't want to add a line for it since it was added
141
+ # TODO: Add an option to remove cached (`git rm --cached`)
142
+ gitignore_path = os.path.join(target_repo.working_dir, ".gitignore")
143
+ if os.path.isfile(gitignore_path):
144
+ with open(gitignore_path) as f:
145
+ gitignore_txt = f.read()
146
+ lines = gitignore_txt.splitlines()
147
+ if target_path in lines:
148
+ return
149
+ with open(gitignore_path, "a") as f:
150
+ if (
151
+ os.path.isfile(gitignore_path)
152
+ and os.path.getsize(gitignore_path) > 0
153
+ ):
154
+ f.write("\n")
155
+ f.write(f"{target_path}\n")
156
+ return True
157
+
158
+
159
+ def ensure_path_is_not_ignored(
160
+ repo: git.Repo, path: str | PathLike
161
+ ) -> None | bool:
162
+ """Ensure a path is not ignored by Git."""
163
+ # Resolve whether the unignore rule belongs to this repo or a submodule.
164
+ target_repo, target_path = _resolve_repo_and_ignore_path(repo, path)
165
+ # No-op if Git does not ignore this path.
166
+ if not target_repo.ignored(target_path):
167
+ return
168
+ matching_gitignore_path, matched_pattern = _get_matching_gitignore_details(
169
+ target_repo, target_path
170
+ )
171
+ if matching_gitignore_path is not None:
172
+ gitignore_path = matching_gitignore_path.as_posix()
173
+ path_for_gitignore = (
174
+ (Path(target_repo.working_dir) / target_path)
175
+ .resolve()
176
+ .relative_to(matching_gitignore_path.parent.resolve())
177
+ .as_posix()
178
+ )
179
+ else:
180
+ gitignore_path = os.path.join(target_repo.working_dir, ".gitignore")
181
+ path_for_gitignore = target_path
182
+ if not os.path.isfile(gitignore_path):
183
+ with open(gitignore_path, "w") as f:
184
+ f.write(f"!{path_for_gitignore}\n")
185
+ return True
186
+ with open(gitignore_path) as f:
187
+ gitignore_txt = f.read()
188
+ lines = gitignore_txt.splitlines()
189
+ direct_rule_variants = [path_for_gitignore, f"/{path_for_gitignore}"]
190
+ if matched_pattern is not None and matched_pattern.startswith("/"):
191
+ no_ignore_line = f"!/{path_for_gitignore}"
192
+ else:
193
+ no_ignore_line = f"!{path_for_gitignore}"
194
+ path_parts = Path(path_for_gitignore).parts
195
+
196
+ def ancestor_requires_recursive_unignore() -> bool:
197
+ """Return True if any ancestor-level ignore rule would block this path.
198
+
199
+ This includes explicit directory ignores (e.g. 'dir/' or '/dir/')
200
+ as well as ancestor-based glob patterns like 'dir/*' or '/dir/*',
201
+ i.e., any rule that would prevent reaching the nested path without
202
+ adding recursive unignore patterns.
203
+ """
204
+ for i in range(1, len(path_parts)):
205
+ ancestor = "/".join(path_parts[:i])
206
+ if (
207
+ ancestor in lines
208
+ or f"/{ancestor}" in lines
209
+ or f"{ancestor}/" in lines
210
+ or f"/{ancestor}/" in lines
211
+ or f"{ancestor}/*" in lines
212
+ or f"/{ancestor}/*" in lines
213
+ ):
214
+ return True
215
+ return False
216
+
217
+ if len(path_parts) == 1:
218
+ # Simple (non-nested) path: remove the direct ignore rule, or add a
219
+ # negation if the ignore comes from a glob or other pattern
220
+ direct_rule = next(
221
+ (rule for rule in direct_rule_variants if rule in lines), None
222
+ )
223
+ if direct_rule is not None:
224
+ lines.remove(direct_rule)
225
+ else:
226
+ # Remove any stale negation and re-append at the end so it takes
227
+ # precedence over any later re-ignore rule
228
+ if no_ignore_line in lines:
229
+ lines.remove(no_ignore_line)
230
+ lines.append(no_ignore_line)
231
+ else:
232
+ # Nested path: only apply recursive un-ignore rules when an ancestor
233
+ # directory is explicitly ignored
234
+ # Otherwise, remove a direct ignore
235
+ # rule for this path or add a simple negation if needed
236
+ removed_direct_rule = False
237
+ direct_rule = next(
238
+ (rule for rule in direct_rule_variants if rule in lines), None
239
+ )
240
+ if direct_rule is not None:
241
+ lines.remove(direct_rule)
242
+ removed_direct_rule = True
243
+ if ancestor_requires_recursive_unignore():
244
+ # Git will not traverse into a directory excluded by a "dir/"
245
+ # pattern, so a bare "!dir/sub/file" negation has no effect.
246
+ # We need to:
247
+ # 1. Convert any "ancestor/" (or "ancestor") exclude to
248
+ # "ancestor/*" so git traverses the directory while still
249
+ # ignoring direct children by default.
250
+ # 2. Add "!ancestor/" rules for intermediate directories.
251
+ # 3. Add "ancestor/*" re-ignore rules for each intermediate dir.
252
+ # 4. Add "!target_path" for the specific file.
253
+ for i in range(1, len(path_parts)):
254
+ ancestor = "/".join(path_parts[:i])
255
+ reignore_glob = f"{ancestor}/*"
256
+ if f"{ancestor}/" in lines:
257
+ idx = lines.index(f"{ancestor}/")
258
+ lines[idx] = reignore_glob
259
+ elif f"/{ancestor}/" in lines:
260
+ idx = lines.index(f"/{ancestor}/")
261
+ lines[idx] = f"/{ancestor}/*"
262
+ elif ancestor in lines:
263
+ idx = lines.index(ancestor)
264
+ lines[idx] = reignore_glob
265
+ elif f"/{ancestor}" in lines:
266
+ idx = lines.index(f"/{ancestor}")
267
+ lines[idx] = f"/{ancestor}/*"
268
+ no_ignore_dir = f"!{ancestor}/"
269
+ anchored_no_ignore_dir = f"!/{ancestor}/"
270
+ # The first ancestor does not need an explicit un-ignore once
271
+ # converted to "ancestor/*". Deeper ancestors do.
272
+ if i > 1:
273
+ # Remove stale entry and re-append so it takes precedence
274
+ if no_ignore_dir in lines:
275
+ lines.remove(no_ignore_dir)
276
+ elif anchored_no_ignore_dir in lines:
277
+ lines.remove(anchored_no_ignore_dir)
278
+ lines.append(no_ignore_dir)
279
+ if (
280
+ reignore_glob not in lines
281
+ and f"/{ancestor}/*" not in lines
282
+ ):
283
+ lines.append(reignore_glob)
284
+ # Remove stale negation and re-append at the end so it takes
285
+ # precedence over any later re-ignore rule
286
+ if no_ignore_line in lines:
287
+ lines.remove(no_ignore_line)
288
+ lines.append(no_ignore_line)
289
+ elif not removed_direct_rule:
290
+ # The path may be ignored by a non-directory pattern (e.g., glob);
291
+ # remove stale negation and append at end so it takes precedence
292
+ if no_ignore_line in lines:
293
+ lines.remove(no_ignore_line)
294
+ lines.append(no_ignore_line)
295
+ with open(gitignore_path, "w") as f:
296
+ f.write(os.linesep.join(lines))
297
+ return True
@@ -241,25 +241,15 @@ def to_dvc(
241
241
  outputs += stage.notebook_outputs
242
242
  elif stage.kind == "sbatch":
243
243
  outputs.append(stage.log_output)
244
- # Build the set of current output paths so we can detect stale
245
- # .gitignore entries from the previous version of the stage
246
- current_out_paths = set()
247
- for out in outputs:
248
- if isinstance(out, PathOutput):
249
- current_out_paths.add(out.path)
250
- elif isinstance(out, str):
251
- current_out_paths.add(out)
244
+ # Build the set of current DVC output paths so we can detect stale
245
+ # .gitignore entries from the previous version of the stage,
246
+ # including synthesized outputs like LaTeX PDFs
247
+ current_out_paths = set(calkit.dvc.out_paths_from_stage(dvc_stage))
252
248
  # If this stage already existed, un-ignore any outputs that have
253
249
  # been renamed or removed so .gitignore does not accumulate stale
254
250
  # entries (e.g., after a capitalization change in the path)
255
251
  old_stage = existing_dvc_stages.get(stage_name, {})
256
- for old_out in old_stage.get("outs", []):
257
- if isinstance(old_out, str):
258
- old_path = old_out
259
- elif isinstance(old_out, dict):
260
- old_path = list(old_out.keys())[0]
261
- else:
262
- continue
252
+ for old_path in calkit.dvc.out_paths_from_stage(old_stage):
263
253
  if old_path not in current_out_paths:
264
254
  calkit.git.ensure_path_is_not_ignored(repo, path=old_path)
265
255
  # Deal with any gitignore changes necessary
@@ -105,7 +105,7 @@ def test_ensure_path_is_not_ignored_nested(tmp_dir):
105
105
 
106
106
  When a parent directory is excluded with a trailing slash (e.g.,
107
107
  ``results/``), git will not traverse into it, so a simple negation like
108
- ``!results/StageName/end.json`` has no effect. The fix converts the
108
+ ``!results/StageName/end.json`` has no effect. The fix converts the
109
109
  directory exclude to a glob pattern and adds intermediate un-ignore rules.
110
110
  """
111
111
  repo = git.Repo.init()
@@ -124,15 +124,13 @@ def test_ensure_path_is_not_ignored_nested(tmp_dir):
124
124
  assert result is True
125
125
  with open(".gitignore") as f:
126
126
  lines = f.read().splitlines()
127
- # The plain directory exclude should be replaced with a glob
128
- assert "results/" not in lines
129
- assert "results/*" in lines
130
- # Intermediate directory must be un-ignored so git traverses into it
131
- assert "!results/StageName/" in lines
132
- # The intermediate directory's other contents must be re-ignored
133
- assert "results/StageName/*" in lines
134
- # The specific file must be explicitly un-ignored
135
- assert "!results/StageName/end.json" in lines
127
+ # Keep the rules minimal while preserving the required behavior
128
+ assert lines == [
129
+ "results/*",
130
+ "!results/StageName/",
131
+ "results/StageName/*",
132
+ "!results/StageName/end.json",
133
+ ]
136
134
  # Verify git no longer considers the target file as ignored
137
135
  assert not repo.ignored("results/StageName/end.json")
138
136
  # Other files in results/ must still be ignored
@@ -148,3 +146,54 @@ def test_ensure_path_is_not_ignored_nested(tmp_dir):
148
146
  repo, path="results/StageName/end.json"
149
147
  )
150
148
  assert result2 is None
149
+
150
+
151
+ def test_ensure_path_is_not_ignored_nested_direct_path_rule(tmp_dir):
152
+ """Unignoring a directly ignored nested path should only remove that
153
+ rule.
154
+ """
155
+ repo = git.Repo.init()
156
+ target = "pubs/applied-ocean-research-model/references.bib"
157
+ sibling = "pubs/applied-ocean-research-model/paper.pdf"
158
+ with open(".gitignore", "w") as f:
159
+ f.write(f"{target}\n")
160
+ os.makedirs("pubs/applied-ocean-research-model", exist_ok=True)
161
+ with open(target, "w") as f:
162
+ f.write("@article{test}\n")
163
+ with open(sibling, "w") as f:
164
+ f.write("pdf\n")
165
+ # Only the direct target path should be ignored initially
166
+ assert repo.ignored(target)
167
+ assert not repo.ignored(sibling)
168
+ result = calkit.git.ensure_path_is_not_ignored(repo, path=target)
169
+ assert result is True
170
+ with open(".gitignore") as f:
171
+ lines = f.read().splitlines()
172
+ # Remove only the direct rule, with no recursive ancestor entries
173
+ assert target not in lines
174
+ assert f"!{target}" not in lines
175
+ assert "!pubs/" not in lines
176
+ assert "pubs/*" not in lines
177
+ assert "!pubs/applied-ocean-research-model/" not in lines
178
+ assert "pubs/applied-ocean-research-model/*" not in lines
179
+ assert not repo.ignored(target)
180
+ assert not repo.ignored(sibling)
181
+
182
+
183
+ def test_ensure_path_is_not_ignored_nested_gitignore_direct_path_rule(tmp_dir):
184
+ repo = git.Repo.init()
185
+ os.makedirs("paper", exist_ok=True)
186
+ target = "paper/main.pdf"
187
+ with open("paper/.gitignore", "w") as f:
188
+ f.write("/main.pdf\n")
189
+ with open(target, "w") as f:
190
+ f.write("pdf\n")
191
+ assert repo.ignored(target)
192
+ result = calkit.git.ensure_path_is_not_ignored(repo, path=target)
193
+ assert result is True
194
+ with open("paper/.gitignore") as f:
195
+ lines = f.read().splitlines()
196
+ assert "/main.pdf" not in lines
197
+ assert "!/main.pdf" not in lines
198
+ assert not os.path.exists(".gitignore")
199
+ assert not repo.ignored(target)
@@ -1,5 +1,6 @@
1
1
  """Tests for ``calkit.pipeline``."""
2
2
 
3
+ import os
3
4
  import subprocess
4
5
 
5
6
  import git
@@ -653,9 +654,12 @@ def test_shell_script_stage_allows_non_composite_slurm_env():
653
654
 
654
655
 
655
656
  def test_gitignore_updated_when_stage_output_renamed(tmp_dir):
656
- """When a stage output path is renamed (e.g., from a capitalization change) the
657
- old path should be removed from .gitignore and the new path should be
658
- added.
657
+ """When a stage output path is renamed, stale .gitignore entries are
658
+ replaced.
659
+
660
+ Use the .gitignore contents for verification because case-only renames can
661
+ still appear ignored on case-insensitive filesystems like the default macOS
662
+ setup.
659
663
  """
660
664
  subprocess.check_call(["calkit", "init"])
661
665
  # Stage 1: initial calkit.yaml with output 'b_sparsity_plot.pdf' stored in DVC
@@ -682,6 +686,9 @@ def test_gitignore_updated_when_stage_output_renamed(tmp_dir):
682
686
  # Verify DVC has added the old output path to .gitignore
683
687
  repo = git.Repo(".")
684
688
  assert repo.ignored("b_sparsity_plot.pdf")
689
+ with open(".gitignore") as f:
690
+ lines = f.read().splitlines()
691
+ assert "/b_sparsity_plot.pdf" in lines
685
692
  # Stage 2: rename output (capitalization change) to 'B_sparsity_plot.pdf'
686
693
  ck_info["pipeline"]["stages"]["plot"]["command"] = (
687
694
  "touch B_sparsity_plot.pdf"
@@ -692,6 +699,52 @@ def test_gitignore_updated_when_stage_output_renamed(tmp_dir):
692
699
  with open("calkit.yaml", "w") as f:
693
700
  calkit.ryaml.dump(ck_info, f)
694
701
  subprocess.check_call(["calkit", "run"])
695
- # Old (stale) path should no longer be ignored by git
696
- assert not repo.ignored("b_sparsity_plot.pdf")
702
+ with open(".gitignore") as f:
703
+ lines = f.read().splitlines()
704
+ assert "/b_sparsity_plot.pdf" not in lines
705
+ assert "/B_sparsity_plot.pdf" in lines
697
706
  assert repo.ignored("B_sparsity_plot.pdf")
707
+
708
+
709
+ def test_gitignore_not_unignored_latex_pdf_output(tmp_dir):
710
+ repo = git.Repo.init()
711
+ subprocess.check_call(["calkit", "init"])
712
+ os.makedirs("paper", exist_ok=True)
713
+ with open("paper/.gitignore", "w") as f:
714
+ f.write("/main.pdf\n")
715
+ ck_info = {
716
+ "environments": {
717
+ "tex": {
718
+ "kind": "conda",
719
+ "path": "environment.yaml",
720
+ }
721
+ },
722
+ "pipeline": {
723
+ "stages": {
724
+ "build-paper": {
725
+ "kind": "latex",
726
+ "environment": "tex",
727
+ "target_path": "paper/main.tex",
728
+ "force": True,
729
+ "inputs": [
730
+ "paper/references.bib",
731
+ "paper/aasjournal.bst",
732
+ "paper/aastex631.cls",
733
+ "paper/results.tex",
734
+ "paper/diagrams",
735
+ "paper/figures",
736
+ ],
737
+ }
738
+ }
739
+ },
740
+ }
741
+ with open("calkit.yaml", "w") as f:
742
+ calkit.ryaml.dump(ck_info, f)
743
+ calkit.pipeline.to_dvc(ck_info=ck_info, write=True)
744
+ assert not os.path.exists(".gitignore")
745
+ assert repo.ignored("paper/main.pdf")
746
+ calkit.pipeline.to_dvc(ck_info=ck_info, write=True)
747
+ assert not os.path.exists(".gitignore")
748
+ with open("paper/.gitignore") as f:
749
+ assert f.read().splitlines() == ["/main.pdf"]
750
+ assert repo.ignored("paper/main.pdf")