pyDiffTools 0.1.52__tar.gz → 0.1.53__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 (134) hide show
  1. {pydifftools-0.1.52 → pydifftools-0.1.53}/MANIFEST.in +1 -0
  2. {pydifftools-0.1.52/pyDiffTools.egg-info → pydifftools-0.1.53}/PKG-INFO +58 -9
  3. {pydifftools-0.1.52 → pydifftools-0.1.53}/README.rst +57 -8
  4. {pydifftools-0.1.52 → pydifftools-0.1.53/pyDiffTools.egg-info}/PKG-INFO +58 -9
  5. {pydifftools-0.1.52 → pydifftools-0.1.53}/pyDiffTools.egg-info/SOURCES.txt +2 -0
  6. pydifftools-0.1.53/pydifftools/browser_lifecycle.py +173 -0
  7. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/command_line.py +45 -5
  8. pydifftools-0.1.53/pydifftools/completions/git-mergein +41 -0
  9. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/continuous.py +106 -168
  10. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/flowchart/watch_graph.py +8 -33
  11. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/forward_search.py +5 -0
  12. pydifftools-0.1.53/pydifftools/git_aliases.py +139 -0
  13. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/git_gd.py +8 -51
  14. pydifftools-0.1.53/pydifftools/line_alignment.py +571 -0
  15. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/notebook/fast_build.py +503 -379
  16. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/pdf_diff.py +2 -17
  17. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/wrap_sentences.py +142 -38
  18. {pydifftools-0.1.52 → pydifftools-0.1.53}/pyproject.toml +2 -1
  19. pydifftools-0.1.53/tests/test_browser_lifecycle.py +273 -0
  20. {pydifftools-0.1.52 → pydifftools-0.1.53}/tests/test_command_line_help.py +29 -3
  21. {pydifftools-0.1.52 → pydifftools-0.1.53}/tests/test_continuous_shutdown.py +6 -2
  22. {pydifftools-0.1.52 → pydifftools-0.1.53}/tests/test_forward_search.py +131 -0
  23. pydifftools-0.1.53/tests/test_git_mergein.py +256 -0
  24. {pydifftools-0.1.52 → pydifftools-0.1.53}/tests/test_git_pdf_diff.py +11 -12
  25. pydifftools-0.1.53/tests/test_line_alignment.py +385 -0
  26. {pydifftools-0.1.52 → pydifftools-0.1.53}/tests/test_pdf_diff.py +2 -3
  27. {pydifftools-0.1.52 → pydifftools-0.1.53}/tests/test_wrap_check.py +373 -46
  28. pydifftools-0.1.52/pydifftools/browser_lifecycle.py +0 -141
  29. pydifftools-0.1.52/pydifftools/git_aliases.py +0 -30
  30. pydifftools-0.1.52/pydifftools/line_alignment.py +0 -391
  31. pydifftools-0.1.52/tests/test_browser_lifecycle.py +0 -171
  32. pydifftools-0.1.52/tests/test_line_alignment.py +0 -131
  33. {pydifftools-0.1.52 → pydifftools-0.1.53}/LICENSE.md +0 -0
  34. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/_quarto.yml +0 -0
  35. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/.jupyter_cache/__version__.txt +0 -0
  36. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/.jupyter_cache/executed/1a724af72b16f5a9e607e12b1c721645/base.ipynb +0 -0
  37. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/.jupyter_cache/executed/1b28fc9daac9081847e5161b2c546f8a/base.ipynb +0 -0
  38. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/.jupyter_cache/executed/231f64eee282fa225d1104935cf80a24/base.ipynb +0 -0
  39. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/.jupyter_cache/executed/26e56f6b0ff54851a45145157f2f0dc4/base.ipynb +0 -0
  40. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/.jupyter_cache/executed/311fabd7029ffd050d056e2f316eb50f/base.ipynb +0 -0
  41. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/.jupyter_cache/executed/57da2021e5b156ac3adf01398201c723/base.ipynb +0 -0
  42. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/.jupyter_cache/executed/62b24ea7da75011d92b0f8924faa208d/base.ipynb +0 -0
  43. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/.jupyter_cache/executed/7f1b20d69d889514ab5d1cc92e3cb14f/base.ipynb +0 -0
  44. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/.jupyter_cache/executed/86f74c8c54a87ff892d9b15dd714e8f0/base.ipynb +0 -0
  45. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/.jupyter_cache/executed/88893b4234eac2945d9d6cb2e277f186/base.ipynb +0 -0
  46. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/.jupyter_cache/executed/9a40046ada6f582ee34af00fbdbfb417/base.ipynb +0 -0
  47. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/.jupyter_cache/executed/a1bf4d270d0641ff41faf1d7cce3439a/base.ipynb +0 -0
  48. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/.jupyter_cache/executed/a3789f7d9585a781f2a1c60ce95ff10d/base.ipynb +0 -0
  49. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/.jupyter_cache/executed/be46ecba858d39ad5f0c46902ddf1c02/base.ipynb +0 -0
  50. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/.jupyter_cache/executed/d0cbc57a12f2ccce710a5afd04cc05e7/base.ipynb +0 -0
  51. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/.jupyter_cache/executed/d3e12d320b14228f701231ae32ddd7dd/base.ipynb +0 -0
  52. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/.jupyter_cache/executed/eb434f61555438d020a6970a5dbf9ee8/base.ipynb +0 -0
  53. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/.jupyter_cache/executed/f6b0e73aa7fa029134665d4dde57e096/base.ipynb +0 -0
  54. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/.jupyter_cache/executed/f719a6e4ff09873cb0ffb06ec9d232f9/base.ipynb +0 -0
  55. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/.jupyter_cache/executed/feeee244a7ce3d60e1a227eb604df823/base.ipynb +0 -0
  56. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/.jupyter_cache/global.db +0 -0
  57. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/example.qmd +0 -0
  58. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/example.tex +0 -0
  59. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/index.qmd +0 -0
  60. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/subproject1/.jupyter_cache/__version__.txt +0 -0
  61. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/subproject1/.jupyter_cache/executed/ca90e4df5f4f0583df6554156a68dc7f/base.ipynb +0 -0
  62. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/subproject1/.jupyter_cache/global.db +0 -0
  63. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/subproject1/end_vs_he_sketch.jpg +0 -0
  64. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/subproject1/independent.qmd +0 -0
  65. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/subproject1/index.qmd +0 -0
  66. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/subproject1/tasks.qmd +0 -0
  67. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/subproject1/test_include.qmd +0 -0
  68. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/subproject1/tryforerror.qmd +0 -0
  69. {pydifftools-0.1.52 → pydifftools-0.1.53}/example_notebook/project1/test_include.qmd +0 -0
  70. {pydifftools-0.1.52 → pydifftools-0.1.53}/pyDiffTools.egg-info/dependency_links.txt +0 -0
  71. {pydifftools-0.1.52 → pydifftools-0.1.53}/pyDiffTools.egg-info/entry_points.txt +0 -0
  72. {pydifftools-0.1.52 → pydifftools-0.1.53}/pyDiffTools.egg-info/requires.txt +0 -0
  73. {pydifftools-0.1.52 → pydifftools-0.1.53}/pyDiffTools.egg-info/top_level.txt +0 -0
  74. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/__init__.py +0 -0
  75. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/bibliography.py +0 -0
  76. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/check_numbers.py +0 -0
  77. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/citation_dialog.py +0 -0
  78. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/command_registry.py +0 -0
  79. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/comment_filter_history.json +0 -0
  80. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/comment_functions.py +0 -0
  81. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/comment_migration.py +0 -0
  82. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/comment_tags.lua +0 -0
  83. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/comment_tags_margin.lua +0 -0
  84. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/comment_tags_no_comments.lua +0 -0
  85. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/comment_toggle.js +0 -0
  86. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/comments.css +0 -0
  87. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/comments_author_colors.css +0 -0
  88. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/copy_files.py +0 -0
  89. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/diff-doc.js +0 -0
  90. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/doc_contents.py +0 -0
  91. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/flowchart/__init__.py +0 -0
  92. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/flowchart/comparison.py +0 -0
  93. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/flowchart/dot_to_yaml.py +0 -0
  94. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/flowchart/graph.py +0 -0
  95. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/flowchart/source_jump.js +0 -0
  96. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/flowchart/svg-pan-zoom.min.js +0 -0
  97. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/flowchart/wgrph_view.js +0 -0
  98. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/git_gd_history.py +0 -0
  99. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/git_gd_image.py +0 -0
  100. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/git_gd_qt.py +0 -0
  101. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/html_comments.py +0 -0
  102. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/html_uncomments.py +0 -0
  103. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/log_example.py +0 -0
  104. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/match_spaces.py +0 -0
  105. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/notebook/__init__.py +0 -0
  106. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/notebook/display.py +0 -0
  107. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/notebook/tex_to_qmd.py +0 -0
  108. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/onewordify.py +0 -0
  109. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/onewordify_undo.py +0 -0
  110. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/outline.py +0 -0
  111. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/rearrange_tex.py +0 -0
  112. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/searchacro.py +0 -0
  113. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/separate_comments.py +0 -0
  114. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/source_jump.py +0 -0
  115. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/split_conflict.py +0 -0
  116. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/unseparate_comments.py +0 -0
  117. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/update_check.py +0 -0
  118. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/wrapping_options.py +0 -0
  119. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/xml2xlsx.vbs +0 -0
  120. {pydifftools-0.1.52 → pydifftools-0.1.53}/pydifftools/zotero.py +0 -0
  121. {pydifftools-0.1.52 → pydifftools-0.1.53}/setup.cfg +0 -0
  122. {pydifftools-0.1.52 → pydifftools-0.1.53}/tests/test_bibliography.py +0 -0
  123. {pydifftools-0.1.52 → pydifftools-0.1.53}/tests/test_check_single_use.py +0 -0
  124. {pydifftools-0.1.52 → pydifftools-0.1.53}/tests/test_comment_filters.py +0 -0
  125. {pydifftools-0.1.52 → pydifftools-0.1.53}/tests/test_comment_migration.py +0 -0
  126. {pydifftools-0.1.52 → pydifftools-0.1.53}/tests/test_git_gd_history.py +0 -0
  127. {pydifftools-0.1.52 → pydifftools-0.1.53}/tests/test_git_gd_image.py +0 -0
  128. {pydifftools-0.1.52 → pydifftools-0.1.53}/tests/test_git_gd_qt.py +0 -0
  129. {pydifftools-0.1.52 → pydifftools-0.1.53}/tests/test_rrng.py +0 -0
  130. {pydifftools-0.1.52 → pydifftools-0.1.53}/tests/test_source_jump.py +0 -0
  131. {pydifftools-0.1.52 → pydifftools-0.1.53}/tests/test_tex_to_qmd.py +0 -0
  132. {pydifftools-0.1.52 → pydifftools-0.1.53}/tests/test_update_check.py +0 -0
  133. {pydifftools-0.1.52 → pydifftools-0.1.53}/tests/test_wrapping.py +0 -0
  134. {pydifftools-0.1.52 → pydifftools-0.1.53}/tests/test_zotero.py +0 -0
@@ -1,6 +1,7 @@
1
1
  # In addition to the package file, various *.sh files are included -- these will eventually be converted to python
2
2
  include README.rst
3
3
  include LICENSE.md
4
+ include pydifftools/completions/git-mergein
4
5
  include pydifftools/diff-doc.js
5
6
  include pydifftools/xml2xlsx.vbs
6
7
  include pydifftools/comments.css
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyDiffTools
3
- Version: 0.1.52
3
+ Version: 0.1.53
4
4
  Summary: Diff tools
5
5
  Author: J M Franck
6
6
  License: Copyright (c) 2015, jmfranck
@@ -72,6 +72,30 @@ included are (listed in order of fun/utility):
72
72
  `filename.md`, build the result,
73
73
  and displays it in your browser.
74
74
 
75
+ Source diff linting minimizes line-break changes against the Git index
76
+ (staging area) by default, matching plain ``git diff``. Use
77
+ ``pydifft cpb filename.md --diff-base @`` to compare against HEAD, or
78
+ ``--diff-base jf_last`` to compare against a tag, branch, or commit hash.
79
+ The selected baseline is read again on each build. An unreadable explicit
80
+ baseline stops the build; an unavailable index baseline, such as for an
81
+ untracked file, falls back to ordinary source linting.
82
+ All diff-minimizing line-break fixes are applied automatically before
83
+ their notice appears. Each page shows as many changed hunks as fit in the
84
+ window, with a blank line separating hunks; larger changes are applied
85
+ just like single-break changes.
86
+ Diff alignment has separate eligibility rules from prose linting.
87
+ Equations and HTML comment contents participate in alignment, including
88
+ hunks that begin inside them. Display equations and hidden text remain
89
+ exempt from prose wrapping and sentence rules. Equation delimiters are
90
+ matched independently of their contents, and surviving matches on either
91
+ side of an edit restore whitespace while preserving changed values and
92
+ TeX commands. Literal delimiters in code are preserved.
93
+ Leading whitespace at matching line starts is restored in top-level
94
+ prose when both margins contain at most three spaces, and inside HTML
95
+ comments and equations. Container indentation (lists, definitions, and
96
+ quotations), tabs or four-space code indentation outside those spans,
97
+ and protected blocks retain their current margins.
98
+
75
99
  Continuous pandoc build.
76
100
  This works *very well* together
77
101
  with the `g/` vim command
@@ -205,6 +229,12 @@ included are (listed in order of fun/utility):
205
229
  ``_build``/``_display`` directories; with ``--watch`` it starts the HTTP
206
230
  server and automatically rebuilds the staged fragments whenever you edit
207
231
  a ``.qmd`` file.
232
+ Browser previews in ``qmdb``, ``cpb``, and ``wgrph`` share the same
233
+ Selenium launcher: Chrome/Chromium is preferred, with Firefox as a
234
+ fallback. Installed ``chromedriver`` and ``geckodriver`` are used directly
235
+ without online driver discovery; if only ``geckodriver`` is installed,
236
+ Firefox opens directly. With neither driver installed, Selenium handles
237
+ discovery. Chrome startup failures are reported before trying Firefox.
208
238
  - ``pydifft tree`` (or ``git tree`` with the alias installed) opens the last
209
239
  40 commits across all branches, in Git date order. Use the down arrow
210
240
  below the history to add the next 40 commits. Colors follow
@@ -215,7 +245,7 @@ included are (listed in order of fun/utility):
215
245
  or set a commit as the comparison endpoint. The right-click menu can restore
216
246
  the working-directory endpoint. Diff window titles show the equivalent
217
247
  arguments, preferring tags, then branch names, then six-character hashes.
218
- Run ``pydifft tree --install`` to add the matching ``git tree`` alias.
248
+ Run ``pydifft --add_to_git tree`` to add the matching ``git tree`` alias.
219
249
  - ``pydifft gd`` (or ``git gd`` with the alias installed) reviews unstaged
220
250
  changes, like bare ``git diff``.
221
251
  ``pydifft gd [git diff args...]`` shows the same Qt review table as the old
@@ -229,12 +259,31 @@ included are (listed in order of fun/utility):
229
259
  among the original, difference, and aligned new image. Images with an
230
260
  alpha channel are displayed over a checkerboard; when transparency
231
261
  changes, an additional red/blue alpha-difference view is included.
232
- Run ``pydifft gd --install`` to add the matching ``git gd`` alias to
233
- your global git config.
234
- ``pydifft gd --add-to-git`` offers the same installation, retaining the
235
- exact command of an existing global ``gd`` alias. If none is configured,
236
- it installs ``!f() { pydifft gd "$@"; }; f``. Alias installation is shared
237
- with ``pd`` and ``tree``.
262
+ Run ``pydifft --add_to_git gd`` to install the global ``git gd`` alias,
263
+ retaining the exact command of an existing global ``gd`` alias. If none
264
+ is configured, it installs ``!f() { pydifft gd "$@"; }; f``.
265
+ Configure ``difftool.mygvim.cmd`` for the GUI diff tool.
266
+ - ``pydifft --add_to_git gd pd tree mergein`` installs any selected subset
267
+ of these global Git aliases in one invocation. Installation appears
268
+ alongside the subcommands in root help; the former per-command
269
+ ``--install`` and ``--add-to-git`` flags have been removed.
270
+ - ``pydifft mergein BRANCH [--remote REMOTE]`` updates an incoming local
271
+ branch without switching branches, then merges it into the current
272
+ branch. Install ``git mergein`` with ``pydifft --add_to_git mergein``.
273
+ ``git mergein incoming/topic`` runs ``git fetch origin
274
+ incoming/topic:incoming/topic`` followed by ``git merge --no-ff
275
+ incoming/topic``. Use ``--remote upstream`` to select another remote;
276
+ ``origin`` is the default. Fetch failures stop the sequence, local
277
+ branch updates are not forced, and merge conflicts remain available
278
+ for normal Git resolution.
279
+
280
+ Installing ``mergein`` also installs its Bash completion in the user
281
+ Bash completion directory (honoring ``BASH_COMPLETION_USER_DIR`` and
282
+ ``XDG_DATA_HOME``). With standard Git Bash completion enabled,
283
+ ``git mergein`` suggests cached branches of the selected remote, using
284
+ bare branch names such as ``incoming/topic``. Completion does not
285
+ contact the remote; fetch to refresh the suggestions. The ``pydifft``
286
+ form supports the same suggestions when argcomplete is enabled.
238
287
  - `pydifft qmdinit [directory]` scaffolds a new Quarto-style project using
239
288
  the bundled templates and example ``project1`` hierarchy, then downloads
240
289
  MathJax into ``_template/mathjax`` so the builder can run immediately.
@@ -300,7 +349,7 @@ included are (listed in order of fun/utility):
300
349
  The former ``git-latexdiffnocompile.sh`` is a compatibility wrapper for
301
350
  ``pydifft pd --no-compile``.
302
351
 
303
- Run ``pydifft pd --add-to-git`` to install the global ``git pd`` alias.
352
+ Run ``pydifft --add_to_git pd`` to install the global ``git pd`` alias.
304
353
  It accepts Git diff arguments: ``git pd 00ffaa -- filename.md`` compares
305
354
  that revision with the working file; bare ``git pd`` compares unstaged
306
355
  changes; ``git pd --cached`` compares staged changes; and
@@ -17,6 +17,30 @@ included are (listed in order of fun/utility):
17
17
  `filename.md`, build the result,
18
18
  and displays it in your browser.
19
19
 
20
+ Source diff linting minimizes line-break changes against the Git index
21
+ (staging area) by default, matching plain ``git diff``. Use
22
+ ``pydifft cpb filename.md --diff-base @`` to compare against HEAD, or
23
+ ``--diff-base jf_last`` to compare against a tag, branch, or commit hash.
24
+ The selected baseline is read again on each build. An unreadable explicit
25
+ baseline stops the build; an unavailable index baseline, such as for an
26
+ untracked file, falls back to ordinary source linting.
27
+ All diff-minimizing line-break fixes are applied automatically before
28
+ their notice appears. Each page shows as many changed hunks as fit in the
29
+ window, with a blank line separating hunks; larger changes are applied
30
+ just like single-break changes.
31
+ Diff alignment has separate eligibility rules from prose linting.
32
+ Equations and HTML comment contents participate in alignment, including
33
+ hunks that begin inside them. Display equations and hidden text remain
34
+ exempt from prose wrapping and sentence rules. Equation delimiters are
35
+ matched independently of their contents, and surviving matches on either
36
+ side of an edit restore whitespace while preserving changed values and
37
+ TeX commands. Literal delimiters in code are preserved.
38
+ Leading whitespace at matching line starts is restored in top-level
39
+ prose when both margins contain at most three spaces, and inside HTML
40
+ comments and equations. Container indentation (lists, definitions, and
41
+ quotations), tabs or four-space code indentation outside those spans,
42
+ and protected blocks retain their current margins.
43
+
20
44
  Continuous pandoc build.
21
45
  This works *very well* together
22
46
  with the `g/` vim command
@@ -150,6 +174,12 @@ included are (listed in order of fun/utility):
150
174
  ``_build``/``_display`` directories; with ``--watch`` it starts the HTTP
151
175
  server and automatically rebuilds the staged fragments whenever you edit
152
176
  a ``.qmd`` file.
177
+ Browser previews in ``qmdb``, ``cpb``, and ``wgrph`` share the same
178
+ Selenium launcher: Chrome/Chromium is preferred, with Firefox as a
179
+ fallback. Installed ``chromedriver`` and ``geckodriver`` are used directly
180
+ without online driver discovery; if only ``geckodriver`` is installed,
181
+ Firefox opens directly. With neither driver installed, Selenium handles
182
+ discovery. Chrome startup failures are reported before trying Firefox.
153
183
  - ``pydifft tree`` (or ``git tree`` with the alias installed) opens the last
154
184
  40 commits across all branches, in Git date order. Use the down arrow
155
185
  below the history to add the next 40 commits. Colors follow
@@ -160,7 +190,7 @@ included are (listed in order of fun/utility):
160
190
  or set a commit as the comparison endpoint. The right-click menu can restore
161
191
  the working-directory endpoint. Diff window titles show the equivalent
162
192
  arguments, preferring tags, then branch names, then six-character hashes.
163
- Run ``pydifft tree --install`` to add the matching ``git tree`` alias.
193
+ Run ``pydifft --add_to_git tree`` to add the matching ``git tree`` alias.
164
194
  - ``pydifft gd`` (or ``git gd`` with the alias installed) reviews unstaged
165
195
  changes, like bare ``git diff``.
166
196
  ``pydifft gd [git diff args...]`` shows the same Qt review table as the old
@@ -174,12 +204,31 @@ included are (listed in order of fun/utility):
174
204
  among the original, difference, and aligned new image. Images with an
175
205
  alpha channel are displayed over a checkerboard; when transparency
176
206
  changes, an additional red/blue alpha-difference view is included.
177
- Run ``pydifft gd --install`` to add the matching ``git gd`` alias to
178
- your global git config.
179
- ``pydifft gd --add-to-git`` offers the same installation, retaining the
180
- exact command of an existing global ``gd`` alias. If none is configured,
181
- it installs ``!f() { pydifft gd "$@"; }; f``. Alias installation is shared
182
- with ``pd`` and ``tree``.
207
+ Run ``pydifft --add_to_git gd`` to install the global ``git gd`` alias,
208
+ retaining the exact command of an existing global ``gd`` alias. If none
209
+ is configured, it installs ``!f() { pydifft gd "$@"; }; f``.
210
+ Configure ``difftool.mygvim.cmd`` for the GUI diff tool.
211
+ - ``pydifft --add_to_git gd pd tree mergein`` installs any selected subset
212
+ of these global Git aliases in one invocation. Installation appears
213
+ alongside the subcommands in root help; the former per-command
214
+ ``--install`` and ``--add-to-git`` flags have been removed.
215
+ - ``pydifft mergein BRANCH [--remote REMOTE]`` updates an incoming local
216
+ branch without switching branches, then merges it into the current
217
+ branch. Install ``git mergein`` with ``pydifft --add_to_git mergein``.
218
+ ``git mergein incoming/topic`` runs ``git fetch origin
219
+ incoming/topic:incoming/topic`` followed by ``git merge --no-ff
220
+ incoming/topic``. Use ``--remote upstream`` to select another remote;
221
+ ``origin`` is the default. Fetch failures stop the sequence, local
222
+ branch updates are not forced, and merge conflicts remain available
223
+ for normal Git resolution.
224
+
225
+ Installing ``mergein`` also installs its Bash completion in the user
226
+ Bash completion directory (honoring ``BASH_COMPLETION_USER_DIR`` and
227
+ ``XDG_DATA_HOME``). With standard Git Bash completion enabled,
228
+ ``git mergein`` suggests cached branches of the selected remote, using
229
+ bare branch names such as ``incoming/topic``. Completion does not
230
+ contact the remote; fetch to refresh the suggestions. The ``pydifft``
231
+ form supports the same suggestions when argcomplete is enabled.
183
232
  - `pydifft qmdinit [directory]` scaffolds a new Quarto-style project using
184
233
  the bundled templates and example ``project1`` hierarchy, then downloads
185
234
  MathJax into ``_template/mathjax`` so the builder can run immediately.
@@ -245,7 +294,7 @@ included are (listed in order of fun/utility):
245
294
  The former ``git-latexdiffnocompile.sh`` is a compatibility wrapper for
246
295
  ``pydifft pd --no-compile``.
247
296
 
248
- Run ``pydifft pd --add-to-git`` to install the global ``git pd`` alias.
297
+ Run ``pydifft --add_to_git pd`` to install the global ``git pd`` alias.
249
298
  It accepts Git diff arguments: ``git pd 00ffaa -- filename.md`` compares
250
299
  that revision with the working file; bare ``git pd`` compares unstaged
251
300
  changes; ``git pd --cached`` compares staged changes; and
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyDiffTools
3
- Version: 0.1.52
3
+ Version: 0.1.53
4
4
  Summary: Diff tools
5
5
  Author: J M Franck
6
6
  License: Copyright (c) 2015, jmfranck
@@ -72,6 +72,30 @@ included are (listed in order of fun/utility):
72
72
  `filename.md`, build the result,
73
73
  and displays it in your browser.
74
74
 
75
+ Source diff linting minimizes line-break changes against the Git index
76
+ (staging area) by default, matching plain ``git diff``. Use
77
+ ``pydifft cpb filename.md --diff-base @`` to compare against HEAD, or
78
+ ``--diff-base jf_last`` to compare against a tag, branch, or commit hash.
79
+ The selected baseline is read again on each build. An unreadable explicit
80
+ baseline stops the build; an unavailable index baseline, such as for an
81
+ untracked file, falls back to ordinary source linting.
82
+ All diff-minimizing line-break fixes are applied automatically before
83
+ their notice appears. Each page shows as many changed hunks as fit in the
84
+ window, with a blank line separating hunks; larger changes are applied
85
+ just like single-break changes.
86
+ Diff alignment has separate eligibility rules from prose linting.
87
+ Equations and HTML comment contents participate in alignment, including
88
+ hunks that begin inside them. Display equations and hidden text remain
89
+ exempt from prose wrapping and sentence rules. Equation delimiters are
90
+ matched independently of their contents, and surviving matches on either
91
+ side of an edit restore whitespace while preserving changed values and
92
+ TeX commands. Literal delimiters in code are preserved.
93
+ Leading whitespace at matching line starts is restored in top-level
94
+ prose when both margins contain at most three spaces, and inside HTML
95
+ comments and equations. Container indentation (lists, definitions, and
96
+ quotations), tabs or four-space code indentation outside those spans,
97
+ and protected blocks retain their current margins.
98
+
75
99
  Continuous pandoc build.
76
100
  This works *very well* together
77
101
  with the `g/` vim command
@@ -205,6 +229,12 @@ included are (listed in order of fun/utility):
205
229
  ``_build``/``_display`` directories; with ``--watch`` it starts the HTTP
206
230
  server and automatically rebuilds the staged fragments whenever you edit
207
231
  a ``.qmd`` file.
232
+ Browser previews in ``qmdb``, ``cpb``, and ``wgrph`` share the same
233
+ Selenium launcher: Chrome/Chromium is preferred, with Firefox as a
234
+ fallback. Installed ``chromedriver`` and ``geckodriver`` are used directly
235
+ without online driver discovery; if only ``geckodriver`` is installed,
236
+ Firefox opens directly. With neither driver installed, Selenium handles
237
+ discovery. Chrome startup failures are reported before trying Firefox.
208
238
  - ``pydifft tree`` (or ``git tree`` with the alias installed) opens the last
209
239
  40 commits across all branches, in Git date order. Use the down arrow
210
240
  below the history to add the next 40 commits. Colors follow
@@ -215,7 +245,7 @@ included are (listed in order of fun/utility):
215
245
  or set a commit as the comparison endpoint. The right-click menu can restore
216
246
  the working-directory endpoint. Diff window titles show the equivalent
217
247
  arguments, preferring tags, then branch names, then six-character hashes.
218
- Run ``pydifft tree --install`` to add the matching ``git tree`` alias.
248
+ Run ``pydifft --add_to_git tree`` to add the matching ``git tree`` alias.
219
249
  - ``pydifft gd`` (or ``git gd`` with the alias installed) reviews unstaged
220
250
  changes, like bare ``git diff``.
221
251
  ``pydifft gd [git diff args...]`` shows the same Qt review table as the old
@@ -229,12 +259,31 @@ included are (listed in order of fun/utility):
229
259
  among the original, difference, and aligned new image. Images with an
230
260
  alpha channel are displayed over a checkerboard; when transparency
231
261
  changes, an additional red/blue alpha-difference view is included.
232
- Run ``pydifft gd --install`` to add the matching ``git gd`` alias to
233
- your global git config.
234
- ``pydifft gd --add-to-git`` offers the same installation, retaining the
235
- exact command of an existing global ``gd`` alias. If none is configured,
236
- it installs ``!f() { pydifft gd "$@"; }; f``. Alias installation is shared
237
- with ``pd`` and ``tree``.
262
+ Run ``pydifft --add_to_git gd`` to install the global ``git gd`` alias,
263
+ retaining the exact command of an existing global ``gd`` alias. If none
264
+ is configured, it installs ``!f() { pydifft gd "$@"; }; f``.
265
+ Configure ``difftool.mygvim.cmd`` for the GUI diff tool.
266
+ - ``pydifft --add_to_git gd pd tree mergein`` installs any selected subset
267
+ of these global Git aliases in one invocation. Installation appears
268
+ alongside the subcommands in root help; the former per-command
269
+ ``--install`` and ``--add-to-git`` flags have been removed.
270
+ - ``pydifft mergein BRANCH [--remote REMOTE]`` updates an incoming local
271
+ branch without switching branches, then merges it into the current
272
+ branch. Install ``git mergein`` with ``pydifft --add_to_git mergein``.
273
+ ``git mergein incoming/topic`` runs ``git fetch origin
274
+ incoming/topic:incoming/topic`` followed by ``git merge --no-ff
275
+ incoming/topic``. Use ``--remote upstream`` to select another remote;
276
+ ``origin`` is the default. Fetch failures stop the sequence, local
277
+ branch updates are not forced, and merge conflicts remain available
278
+ for normal Git resolution.
279
+
280
+ Installing ``mergein`` also installs its Bash completion in the user
281
+ Bash completion directory (honoring ``BASH_COMPLETION_USER_DIR`` and
282
+ ``XDG_DATA_HOME``). With standard Git Bash completion enabled,
283
+ ``git mergein`` suggests cached branches of the selected remote, using
284
+ bare branch names such as ``incoming/topic``. Completion does not
285
+ contact the remote; fetch to refresh the suggestions. The ``pydifft``
286
+ form supports the same suggestions when argcomplete is enabled.
238
287
  - `pydifft qmdinit [directory]` scaffolds a new Quarto-style project using
239
288
  the bundled templates and example ``project1`` hierarchy, then downloads
240
289
  MathJax into ``_template/mathjax`` so the builder can run immediately.
@@ -300,7 +349,7 @@ included are (listed in order of fun/utility):
300
349
  The former ``git-latexdiffnocompile.sh`` is a compatibility wrapper for
301
350
  ``pydifft pd --no-compile``.
302
351
 
303
- Run ``pydifft pd --add-to-git`` to install the global ``git pd`` alias.
352
+ Run ``pydifft --add_to_git pd`` to install the global ``git pd`` alias.
304
353
  It accepts Git diff arguments: ``git pd 00ffaa -- filename.md`` compares
305
354
  that revision with the working file; bare ``git pd`` compares unstaged
306
355
  changes; ``git pd --cached`` compares staged changes; and
@@ -90,6 +90,7 @@ pydifftools/wrap_sentences.py
90
90
  pydifftools/wrapping_options.py
91
91
  pydifftools/xml2xlsx.vbs
92
92
  pydifftools/zotero.py
93
+ pydifftools/completions/git-mergein
93
94
  pydifftools/flowchart/__init__.py
94
95
  pydifftools/flowchart/comparison.py
95
96
  pydifftools/flowchart/dot_to_yaml.py
@@ -113,6 +114,7 @@ tests/test_forward_search.py
113
114
  tests/test_git_gd_history.py
114
115
  tests/test_git_gd_image.py
115
116
  tests/test_git_gd_qt.py
117
+ tests/test_git_mergein.py
116
118
  tests/test_git_pdf_diff.py
117
119
  tests/test_line_alignment.py
118
120
  tests/test_pdf_diff.py
@@ -0,0 +1,173 @@
1
+ import os
2
+ import re
3
+ import shutil
4
+ import subprocess
5
+ import sys
6
+
7
+ # callbacks that run before any dialog needs the user; cpb registers one
8
+ # that closes its live preview, so issues are dealt with before it reopens
9
+ dialog_callbacks = []
10
+
11
+
12
+ # also used by: continuous.py, notebook/fast_build.py, flowchart/watch_graph.py
13
+ def start_browser():
14
+ """Prefer Chrome/Chromium, using installed drivers before discovery.
15
+
16
+ If only GeckoDriver is installed, use Firefox directly. Otherwise try
17
+ Chrome first, including version-mismatch recovery, then Firefox.
18
+ """
19
+ from selenium import webdriver
20
+ from selenium.common.exceptions import (
21
+ SessionNotCreatedException,
22
+ WebDriverException,
23
+ )
24
+ from selenium.webdriver.chrome.service import Service as ChromeService
25
+ from selenium.webdriver.firefox.service import Service as FirefoxService
26
+
27
+ chrome_driver = shutil.which("chromedriver")
28
+ firefox_driver = shutil.which("geckodriver")
29
+ chrome_error = None
30
+ if chrome_driver or not firefox_driver:
31
+ # {{{ Use installed ChromeDriver without an online Manager lookup
32
+ kwargs = {}
33
+ if chrome_driver:
34
+ options = webdriver.ChromeOptions()
35
+ # Debian's driver defaults to Chromium even if Chrome is installed;
36
+ # retain Selenium Manager's preference for Google Chrome.
37
+ browser_path = (
38
+ shutil.which("google-chrome")
39
+ or shutil.which("google-chrome-stable")
40
+ or shutil.which("chromium")
41
+ or shutil.which("chromium-browser")
42
+ )
43
+ if browser_path:
44
+ options.binary_location = browser_path
45
+ kwargs = {
46
+ "service": ChromeService(chrome_driver),
47
+ "options": options,
48
+ }
49
+ # }}}
50
+ try:
51
+ try:
52
+ return webdriver.Chrome(**kwargs)
53
+ except SessionNotCreatedException as exc:
54
+ # {{{ Retry ChromeDriver's explicit browser-version rejection
55
+ details = str(exc)
56
+ driver_match = re.search(
57
+ r"ChromeDriver only supports Chrome version (\d+)", details
58
+ )
59
+ browser_match = re.search(
60
+ r"Current browser version is (\d+)", details
61
+ )
62
+ if driver_match is None or browser_match is None:
63
+ raise
64
+ print(
65
+ "pydifft: warning: ChromeDriver "
66
+ f"{driver_match.group(1)} and Chrome "
67
+ f"{browser_match.group(1)} do not match; retrying "
68
+ "with the driver version check disabled.",
69
+ file=sys.stderr,
70
+ )
71
+ service = kwargs.get("service")
72
+ kwargs["service"] = ChromeService(
73
+ executable_path=service.path if service else None,
74
+ service_args=[
75
+ *(service.service_args if service else []),
76
+ "--disable-build-check",
77
+ ],
78
+ )
79
+ return webdriver.Chrome(**kwargs)
80
+ # }}}
81
+ except WebDriverException as exc:
82
+ chrome_error = exc
83
+ print(
84
+ f"pydifft: warning: Chrome/Chromium could not start: {exc}\n"
85
+ "Trying Firefox instead.",
86
+ file=sys.stderr,
87
+ )
88
+
89
+ try:
90
+ if firefox_driver:
91
+ return webdriver.Firefox(service=FirefoxService(firefox_driver))
92
+ return webdriver.Firefox()
93
+ except WebDriverException as exc:
94
+ if chrome_error is not None:
95
+ raise exc from chrome_error
96
+ raise
97
+
98
+
99
+ def prepare_for_dialog():
100
+ """Let watch commands close their preview before a dialog appears."""
101
+ for callback in list(dialog_callbacks):
102
+ callback()
103
+
104
+
105
+ # also used by: continuous.py, notebook/fast_build.py, flowchart/watch_graph.py
106
+ def browser_window_is_alive(browser):
107
+ # Keep all browser liveness checks in one place so watch commands share
108
+ # the same shutdown behavior when a user closes the browser window.
109
+ # Do not probe with execute_script here: page navigations can briefly
110
+ # interrupt script execution even while the window is still open.
111
+ if browser is None:
112
+ return False
113
+ try:
114
+ handles = browser.window_handles
115
+ if not handles:
116
+ return False
117
+ return True
118
+ except Exception:
119
+ return False
120
+
121
+
122
+ def close_browser_window(browser):
123
+ # Close a Selenium browser session and ignore errors from already-closed
124
+ # windows so cleanup paths stay simple.
125
+ if browser is None:
126
+ return
127
+ try:
128
+ browser.quit()
129
+ except Exception:
130
+ pass
131
+
132
+
133
+ # also used by: notebook/fast_build.py (qmdb's forward search), besides
134
+ # cpb in continuous.py; tests/test_continuous_shutdown.py monkeypatches it
135
+ def forward_search_in_browser(browser, search_text):
136
+ # Reuse the same browser-side find logic across cpb and qmdb.
137
+ if browser is None or not search_text:
138
+ return False
139
+ found = browser.execute_script(
140
+ """
141
+ var searchText = arguments[0];
142
+ if (!window.find) {
143
+ return false;
144
+ }
145
+ var didFind = window.find(searchText);
146
+ if (didFind && window.getSelection) {
147
+ var selection = window.getSelection();
148
+ if (selection.rangeCount > 0) {
149
+ var rect = selection.getRangeAt(0).getBoundingClientRect();
150
+ window.scrollBy(0, rect.top - window.innerHeight / 3);
151
+ }
152
+ }
153
+ return didFind;
154
+ """,
155
+ search_text,
156
+ )
157
+ if not found:
158
+ print("forward search did not find text:", search_text)
159
+ # Bring the browser window to the foreground in Linux window managers,
160
+ # but only when it found the text, since every qmdb session hears it.
161
+ if found and os.name == "posix" and shutil.which("wmctrl"):
162
+ window_title = browser.execute_script("return document.title;")
163
+ if window_title:
164
+ # Try common browser title forms used by desktop environments.
165
+ for title_candidate in [
166
+ window_title,
167
+ window_title + " - Google Chrome",
168
+ window_title + " - Chromium",
169
+ window_title + " - Chrome",
170
+ window_title + " - Mozilla Firefox",
171
+ ]:
172
+ subprocess.run(["wmctrl", "-a", title_candidate], check=False)
173
+ return found
@@ -37,6 +37,9 @@ from .searchacro import replace_acros
37
37
  from .rearrange_tex import run as rearrange_tex_run
38
38
  from .git_gd import gd # registers git difftool review command
39
39
  from .pdf_diff import pd, git_pd # registers PDF diff command
40
+ from .git_aliases import (
41
+ GIT_ALIASES, install_git_alias, mergein_completer,
42
+ )
40
43
  from .flowchart.watch_graph import wgrph
41
44
  from .flowchart.graph import load_graph_yaml
42
45
  from .notebook.tex_to_qmd import tex2qmd
@@ -45,6 +48,7 @@ from .notebook.fast_build import (
45
48
  qmdinit,
46
49
  QMDB_FORWARD_SEARCH_HOST,
47
50
  QMDB_FORWARD_SEARCH_PORT,
51
+ QMDB_PORT_ATTEMPTS,
48
52
  )
49
53
 
50
54
  from .command_registry import _COMMAND_SPECS, register_command
@@ -575,13 +579,17 @@ def mfs(text):
575
579
  search_text = text.strip()
576
580
 
577
581
  # Try existing cpb and qmdb listeners before launching a new cpb process.
578
- socket_addresses = [
579
- (FORWARD_SEARCH_HOST, FORWARD_SEARCH_PORT),
580
- (QMDB_FORWARD_SEARCH_HOST, QMDB_FORWARD_SEARCH_PORT),
582
+ # Several qmdb sessions can run at once on consecutive ports; each one
583
+ # receives the search and only a browser that finds the text comes
584
+ # forward.
585
+ socket_addresses = [(FORWARD_SEARCH_HOST, FORWARD_SEARCH_PORT)] + [
586
+ (QMDB_FORWARD_SEARCH_HOST, QMDB_FORWARD_SEARCH_PORT + offset)
587
+ for offset in range(QMDB_PORT_ATTEMPTS)
581
588
  ]
582
589
 
583
590
  def try_existing_listener():
584
591
  protocol_errors = []
592
+ delivered = False
585
593
  for address in socket_addresses:
586
594
  try:
587
595
  send_forward_search(address, search_text)
@@ -590,6 +598,10 @@ def mfs(text):
590
598
  except ForwardSearchProtocolError as exc:
591
599
  protocol_errors.append(str(exc))
592
600
  continue
601
+ if address == socket_addresses[0]:
602
+ return True
603
+ delivered = True
604
+ if delivered:
593
605
  return True
594
606
  if protocol_errors:
595
607
  raise RuntimeError(
@@ -902,8 +914,14 @@ def build_parser():
902
914
  )
903
915
  parser.epilog = _subcommand_help_hint(parser.prog)
904
916
  parser._pydifft_subparsers = {}
905
- subparsers = parser.add_subparsers(dest="command")
906
- subparsers.required = True
917
+ subparsers = parser.add_subparsers(dest="command", title="commands")
918
+ # Consume installer names exclusively, including during completion.
919
+ # argparse does not validate REMAINDER choices; main checks them first.
920
+ subparsers.container.add_argument(
921
+ "--add_to_git", nargs=argparse.REMAINDER, choices=list(GIT_ALIASES),
922
+ help=("Install one or more global Git aliases: gd, pd, tree, mergein "
923
+ "(without a subcommand)."),
924
+ )
907
925
  for name, spec in _COMMAND_SPECS.items():
908
926
  subparser = subparsers.add_parser(
909
927
  name,
@@ -926,6 +944,8 @@ def build_parser():
926
944
  if name == "wgrph" and action.dest == "t":
927
945
  # Offer case-insensitive completions for incomplete task names.
928
946
  action.completer = wgrph_task_completer
947
+ if name == "mergein" and action.dest in ("branch", "remote"):
948
+ action.completer = mergein_completer
929
949
  subparser.set_defaults(_handler=spec["handler"])
930
950
  parser._pydifft_subparsers[name] = subparser
931
951
  return parser
@@ -985,6 +1005,25 @@ def main(argv=None):
985
1005
  parser._pydifft_subparsers[subcommand].print_help()
986
1006
  return
987
1007
  namespace = parser.parse_args(argv)
1008
+ if namespace.add_to_git is not None:
1009
+ if not namespace.add_to_git:
1010
+ parser.error("--add_to_git requires at least one command name")
1011
+ unknown_names = [
1012
+ name for name in namespace.add_to_git if name not in GIT_ALIASES
1013
+ ]
1014
+ if unknown_names:
1015
+ parser.error(
1016
+ "unknown Git alias name(s): " + ", ".join(unknown_names)
1017
+ )
1018
+ if namespace.command is not None:
1019
+ parser.error("--add_to_git cannot be combined with a subcommand")
1020
+ for name in dict.fromkeys(namespace.add_to_git):
1021
+ install_git_alias(
1022
+ name, GIT_ALIASES[name], preserve_existing=(name == "gd"),
1023
+ )
1024
+ return
1025
+ if namespace.command is None:
1026
+ parser.error("a subcommand or --add_to_git is required")
988
1027
  # Shared wrapping arguments are validated identically for each command.
989
1028
  if hasattr(namespace, "wrapnumber") and namespace.wrapnumber < 1:
990
1029
  parser._pydifft_subparsers[namespace.command].error(
@@ -1016,6 +1055,7 @@ def main(argv=None):
1016
1055
  handler_kwargs = dict(vars(namespace))
1017
1056
  handler_kwargs.pop("_handler", None)
1018
1057
  handler_kwargs.pop("command", None)
1058
+ handler_kwargs.pop("add_to_git", None)
1019
1059
  # {{{ explain Chrome and ChromeDriver version mismatches
1020
1060
  from selenium.common.exceptions import SessionNotCreatedException
1021
1061
 
@@ -0,0 +1,41 @@
1
+ # Loaded by Git's Bash completion through _completion_loader git-mergein.
2
+ _git_mergein ()
3
+ {
4
+ local remote=origin branch_seen=0 expect_remote=0 end_options=0
5
+ local i word ref symref ref_prefix candidate
6
+ for ((i = __git_cmd_idx + 1; i < cword; i++)); do
7
+ word=${words[i]}
8
+ if ((expect_remote)); then
9
+ remote=$word
10
+ expect_remote=0
11
+ elif ((end_options)); then
12
+ branch_seen=1
13
+ else
14
+ case "$word" in
15
+ --remote) expect_remote=1 ;;
16
+ --remote=*) remote=${word#--remote=} ;;
17
+ --) end_options=1 ;;
18
+ -*) ;;
19
+ *) branch_seen=1 ;;
20
+ esac
21
+ fi
22
+ done
23
+ if ((expect_remote)); then
24
+ __gitcomp_nl "$(__git remote)"
25
+ elif ((!end_options)) && [[ $cur == --remote=* ]]; then
26
+ __gitcomp_nl "$(__git remote)" "--remote=" "${cur#--remote=}"
27
+ elif ((!end_options)) && [[ $cur == -* ]]; then
28
+ __gitcomp "--remote --help"
29
+ elif ((!branch_seen)); then
30
+ ref_prefix="refs/remotes/$remote/"
31
+ local candidates=
32
+ while read -r ref symref; do
33
+ [[ $ref == "$ref_prefix"* && -z $symref ]] || continue
34
+ candidate=${ref#"$ref_prefix"}
35
+ [[ $candidate == HEAD ]] && continue
36
+ candidates+="$candidate"$'\n'
37
+ done < <(__git for-each-ref --format='%(refname) %(symref)' \
38
+ -- "$ref_prefix" 2>/dev/null)
39
+ __gitcomp_nl "$candidates"
40
+ fi
41
+ }