webdiff 1.2.0__tar.gz → 1.3.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.
- {webdiff-1.2.0 → webdiff-1.3.0}/PKG-INFO +17 -7
- {webdiff-1.2.0 → webdiff-1.3.0}/README.md +13 -4
- {webdiff-1.2.0 → webdiff-1.3.0}/pyproject.toml +5 -4
- webdiff-1.3.0/webdiff/app.py +366 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/argparser.py +7 -3
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/diff.py +20 -3
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/dirdiff.py +5 -5
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/github_fetcher.py +2 -2
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/gitwebdiff.py +1 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/style.css +13 -7
- webdiff-1.3.0/webdiff/static/js/file_diff.js +61 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/templates/file_diff.html +0 -8
- webdiff-1.3.0/webdiff/toy.py +26 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/util.py +19 -0
- webdiff-1.2.0/webdiff/app.py +0 -347
- webdiff-1.2.0/webdiff/static/js/file_diff.js +0 -47
- webdiff-1.2.0/webdiff/static/js/jquery-2.1.1.min.js +0 -4
- {webdiff-1.2.0 → webdiff-1.3.0}/LICENSE +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/__init__.py +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/githubdiff.py +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/gitwebshow.py +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/localfilediff.py +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/options.py +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/__init__.py +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/codediff.js/codediff.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/codediff.js/googlecode.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/Inconsolata/Inconsolata-Bold.ttf +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/Inconsolata/Inconsolata-Regular.ttf +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/Inconsolata/OFL.txt +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/googlecode.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/inconsolata.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/a11y-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/a11y-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/agate.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/an-old-hope.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/androidstudio.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/arduino-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/arta.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/ascetic.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/atom-one-dark-reasonable.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/atom-one-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/atom-one-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/3024.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/apathy.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/apprentice.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/ashes.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-cave-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-cave.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-dune-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-dune.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-estuary-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-estuary.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-forest-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-forest.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-heath-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-heath.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-lakeside-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-lakeside.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-plateau-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-plateau.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-savanna-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-savanna.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-seaside-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-seaside.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-sulphurpool-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-sulphurpool.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atlas.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/bespin.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/black-metal-bathory.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/black-metal-burzum.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/black-metal-dark-funeral.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/black-metal-gorgoroth.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/black-metal-immortal.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/black-metal-khold.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/black-metal-marduk.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/black-metal-mayhem.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/black-metal-nile.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/black-metal-venom.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/black-metal.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/brewer.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/bright.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/brogrammer.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/brush-trees-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/brush-trees.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/chalk.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/circus.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/classic-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/classic-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/codeschool.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/colors.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/cupcake.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/cupertino.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/danqing.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/darcula.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/dark-violet.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/darkmoss.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/darktooth.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/decaf.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/default-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/default-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/dirtysea.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/dracula.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/edge-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/edge-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/eighties.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/embers.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/equilibrium-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/equilibrium-gray-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/equilibrium-gray-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/equilibrium-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/espresso.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/eva-dim.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/eva.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/flat.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/framer.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/fruit-soda.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/gigavolt.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/github.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/google-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/google-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/grayscale-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/grayscale-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/green-screen.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/gruvbox-dark-hard.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/gruvbox-dark-medium.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/gruvbox-dark-pale.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/gruvbox-dark-soft.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/gruvbox-light-hard.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/gruvbox-light-medium.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/gruvbox-light-soft.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/hardcore.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/harmonic16-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/harmonic16-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/heetch-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/heetch-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/helios.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/hopscotch.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/horizon-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/horizon-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/humanoid-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/humanoid-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/ia-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/ia-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/icy-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/ir-black.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/isotope.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/kimber.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/london-tube.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/macintosh.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/marrakesh.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/materia.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/material-darker.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/material-lighter.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/material-palenight.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/material-vivid.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/material.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/mellow-purple.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/mexico-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/mocha.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/monokai.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/nebula.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/nord.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/nova.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/ocean.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/oceanicnext.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/one-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/onedark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/outrun-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/papercolor-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/papercolor-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/paraiso.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/pasque.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/phd.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/pico.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/pop.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/porple.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/qualia.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/railscasts.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/rebecca.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/ros-pine-dawn.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/ros-pine-moon.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/ros-pine.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/sagelight.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/sandcastle.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/seti-ui.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/shapeshifter.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/silk-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/silk-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/snazzy.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/solar-flare-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/solar-flare.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/solarized-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/solarized-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/spacemacs.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/summercamp.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/summerfruit-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/summerfruit-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/synth-midnight-terminal-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/synth-midnight-terminal-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/tango.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/tender.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/tomorrow-night.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/tomorrow.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/twilight.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/unikitty-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/unikitty-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/vulcan.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/windows-10-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/windows-10.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/windows-95-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/windows-95.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/windows-high-contrast-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/windows-high-contrast.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/windows-nt-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/windows-nt.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/woodland.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/xcode-dusk.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/zenburn.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/brown-paper.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/brown-papersq.png +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/codepen-embed.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/color-brewer.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/default.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/devibeans.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/docco.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/far.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/felipec.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/foundation.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/github-dark-dimmed.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/github-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/github.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/gml.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/googlecode.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/gradient-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/gradient-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/grayscale.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/hybrid.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/idea.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/intellij-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/ir-black.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/isbl-editor-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/isbl-editor-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/kimbie-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/kimbie-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/lightfair.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/lioshi.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/magula.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/mono-blue.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/monokai-sublime.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/monokai.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/night-owl.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/nnfx-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/nnfx-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/nord.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/obsidian.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/panda-syntax-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/panda-syntax-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/paraiso-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/paraiso-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/pojoaque.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/pojoaque.jpg +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/purebasic.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/qtcreator-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/qtcreator-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/rainbow.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/routeros.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/school-book.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/shades-of-purple.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/srcery.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/stackoverflow-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/stackoverflow-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/sunburst.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/tokyo-night-dark.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/tokyo-night-light.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/tomorrow-night-blue.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/tomorrow-night-bright.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/vs.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/vs2015.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/xcode.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/xt256.css +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/img/favicon.ico +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/img/sprites.png +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/img/trans_bg.gif +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/js/highlight.min.js +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/js/highlight.pack.min.js +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/templates/__init__.py +0 -0
- {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/unified_diff.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: webdiff
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Two-column web-based git difftool
|
|
5
5
|
Home-page: https://github.com/danvk/webdiff/
|
|
6
6
|
License: Apache-2.0
|
|
@@ -18,10 +18,11 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.11
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
20
|
Classifier: Topic :: Software Development :: Version Control
|
|
21
|
-
Requires-Dist: PyGithub (
|
|
21
|
+
Requires-Dist: PyGithub (>=2.3.0,<3.0.0)
|
|
22
|
+
Requires-Dist: aiohttp (>=3.9.5,<4.0.0)
|
|
22
23
|
Requires-Dist: binaryornot
|
|
23
24
|
Requires-Dist: pillow
|
|
24
|
-
Requires-Dist: unidiff (
|
|
25
|
+
Requires-Dist: unidiff (>=0.7.4,<0.8.0)
|
|
25
26
|
Project-URL: Repository, https://github.com/danvk/webdiff/
|
|
26
27
|
Description-Content-Type: text/markdown
|
|
27
28
|
|
|
@@ -55,6 +56,10 @@ Features include:
|
|
|
55
56
|
|
|
56
57
|
pip install webdiff
|
|
57
58
|
|
|
59
|
+
or using [pipx]:
|
|
60
|
+
|
|
61
|
+
pipx install webdiff
|
|
62
|
+
|
|
58
63
|
or, if you prefer [Homebrew]:
|
|
59
64
|
|
|
60
65
|
brew install danvk/webdiff/webdiff
|
|
@@ -135,8 +140,7 @@ Options are:
|
|
|
135
140
|
poetry install
|
|
136
141
|
cd ts
|
|
137
142
|
yarn
|
|
138
|
-
|
|
139
|
-
NODE_OPTIONS=--openssl-legacy-provider webpack
|
|
143
|
+
yarn build
|
|
140
144
|
|
|
141
145
|
Then from the root directory:
|
|
142
146
|
|
|
@@ -168,7 +172,7 @@ To iterate on the PyPI package, run:
|
|
|
168
172
|
|
|
169
173
|
pip3 uninstall webdiff
|
|
170
174
|
poetry build
|
|
171
|
-
pip3 install dist/webdiff
|
|
175
|
+
pip3 install dist/webdiff-(latest).tar.gz
|
|
172
176
|
|
|
173
177
|
To publish to pypitest:
|
|
174
178
|
|
|
@@ -179,6 +183,8 @@ And to the real pypi:
|
|
|
179
183
|
|
|
180
184
|
poetry publish
|
|
181
185
|
|
|
186
|
+
You can publish pre-release versions to pypi by adding "bN" to the version number.
|
|
187
|
+
|
|
182
188
|
See [pypirc][] and [poetry][] docs for details on setting up tokens for pypi.
|
|
183
189
|
|
|
184
190
|
Publication checklist. Do these from _outside_ the webdiff directory:
|
|
@@ -208,7 +214,10 @@ When you run `git webdiff (args)`, it runs:
|
|
|
208
214
|
|
|
209
215
|
This tells `git` to set up two directories and invoke `webdiff leftdir rightdir`.
|
|
210
216
|
|
|
211
|
-
There
|
|
217
|
+
There are two wrinkles here:
|
|
218
|
+
|
|
219
|
+
- `git difftool -d` may fill one of the sides (typically the right) with symlinks. This is faster than copying files, but unfortunately `git diff --no-index` does not resolve these symlinks. To make this work, if a directory contains symlinks, webdiff makes a copy of it before diffing. For file diffs, it resolves the symlink before passing it to `git diff --no-index`. The upshot is that you can run `git webdiff`, edit a file, reload the browser window and see the changes.
|
|
220
|
+
- `git difftool` cleans up its temporary directories when the main webdiff process terminates. Since webdiff detaches to give you back your terminal, it has to make another copy of the directories (this time without resolving symlinks) to make sure they're still there for the child process.
|
|
212
221
|
|
|
213
222
|
[pypirc]: https://packaging.python.org/specifications/pypirc/
|
|
214
223
|
[Homebrew]: https://brew.sh/
|
|
@@ -216,4 +225,5 @@ There's one complication involving symlinks. `git difftool -d` may fill one of t
|
|
|
216
225
|
[git config]: https://git-scm.com/docs/git-config
|
|
217
226
|
[themes]: http://example.com
|
|
218
227
|
[poetry]: https://python-poetry.org/docs/repositories/#publishable-repositories
|
|
228
|
+
[pipx]: https://pipx.pypa.io/stable/
|
|
219
229
|
|
|
@@ -28,6 +28,10 @@ Features include:
|
|
|
28
28
|
|
|
29
29
|
pip install webdiff
|
|
30
30
|
|
|
31
|
+
or using [pipx]:
|
|
32
|
+
|
|
33
|
+
pipx install webdiff
|
|
34
|
+
|
|
31
35
|
or, if you prefer [Homebrew]:
|
|
32
36
|
|
|
33
37
|
brew install danvk/webdiff/webdiff
|
|
@@ -108,8 +112,7 @@ Options are:
|
|
|
108
112
|
poetry install
|
|
109
113
|
cd ts
|
|
110
114
|
yarn
|
|
111
|
-
|
|
112
|
-
NODE_OPTIONS=--openssl-legacy-provider webpack
|
|
115
|
+
yarn build
|
|
113
116
|
|
|
114
117
|
Then from the root directory:
|
|
115
118
|
|
|
@@ -141,7 +144,7 @@ To iterate on the PyPI package, run:
|
|
|
141
144
|
|
|
142
145
|
pip3 uninstall webdiff
|
|
143
146
|
poetry build
|
|
144
|
-
pip3 install dist/webdiff
|
|
147
|
+
pip3 install dist/webdiff-(latest).tar.gz
|
|
145
148
|
|
|
146
149
|
To publish to pypitest:
|
|
147
150
|
|
|
@@ -152,6 +155,8 @@ And to the real pypi:
|
|
|
152
155
|
|
|
153
156
|
poetry publish
|
|
154
157
|
|
|
158
|
+
You can publish pre-release versions to pypi by adding "bN" to the version number.
|
|
159
|
+
|
|
155
160
|
See [pypirc][] and [poetry][] docs for details on setting up tokens for pypi.
|
|
156
161
|
|
|
157
162
|
Publication checklist. Do these from _outside_ the webdiff directory:
|
|
@@ -181,7 +186,10 @@ When you run `git webdiff (args)`, it runs:
|
|
|
181
186
|
|
|
182
187
|
This tells `git` to set up two directories and invoke `webdiff leftdir rightdir`.
|
|
183
188
|
|
|
184
|
-
There
|
|
189
|
+
There are two wrinkles here:
|
|
190
|
+
|
|
191
|
+
- `git difftool -d` may fill one of the sides (typically the right) with symlinks. This is faster than copying files, but unfortunately `git diff --no-index` does not resolve these symlinks. To make this work, if a directory contains symlinks, webdiff makes a copy of it before diffing. For file diffs, it resolves the symlink before passing it to `git diff --no-index`. The upshot is that you can run `git webdiff`, edit a file, reload the browser window and see the changes.
|
|
192
|
+
- `git difftool` cleans up its temporary directories when the main webdiff process terminates. Since webdiff detaches to give you back your terminal, it has to make another copy of the directories (this time without resolving symlinks) to make sure they're still there for the child process.
|
|
185
193
|
|
|
186
194
|
[pypirc]: https://packaging.python.org/specifications/pypirc/
|
|
187
195
|
[Homebrew]: https://brew.sh/
|
|
@@ -189,3 +197,4 @@ There's one complication involving symlinks. `git difftool -d` may fill one of t
|
|
|
189
197
|
[git config]: https://git-scm.com/docs/git-config
|
|
190
198
|
[themes]: http://example.com
|
|
191
199
|
[poetry]: https://python-poetry.org/docs/repositories/#publishable-repositories
|
|
200
|
+
[pipx]: https://pipx.pypa.io/stable/
|
|
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
4
4
|
|
|
5
5
|
[tool.poetry]
|
|
6
6
|
name = "webdiff"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.3.0"
|
|
8
8
|
description = "Two-column web-based git difftool"
|
|
9
9
|
license = "Apache-2.0"
|
|
10
10
|
readme = "README.md"
|
|
@@ -28,12 +28,13 @@ include = ["webdiff/static/js/file_diff.js"]
|
|
|
28
28
|
python = "^3.10"
|
|
29
29
|
binaryornot = "*"
|
|
30
30
|
pillow = "*"
|
|
31
|
-
PyGithub = "2.3.0"
|
|
32
|
-
unidiff = "
|
|
31
|
+
PyGithub = "^2.3.0"
|
|
32
|
+
unidiff = "^0.7.4"
|
|
33
|
+
aiohttp = "^3.9.5"
|
|
33
34
|
|
|
34
35
|
[tool.poetry.group.dev.dependencies]
|
|
35
36
|
pytest = "^7.1.3"
|
|
36
|
-
ruff = "
|
|
37
|
+
ruff = "0.7"
|
|
37
38
|
|
|
38
39
|
[tool.poetry.scripts]
|
|
39
40
|
webdiff = "webdiff.app:run"
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""Web-based file differ.
|
|
3
|
+
|
|
4
|
+
For usage, see README.md.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import dataclasses
|
|
8
|
+
import importlib.metadata
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
import mimetypes
|
|
12
|
+
import os
|
|
13
|
+
import platform
|
|
14
|
+
import signal
|
|
15
|
+
import socket
|
|
16
|
+
import subprocess
|
|
17
|
+
import sys
|
|
18
|
+
import threading
|
|
19
|
+
import time
|
|
20
|
+
import webbrowser
|
|
21
|
+
|
|
22
|
+
import aiohttp
|
|
23
|
+
import aiohttp.web_request
|
|
24
|
+
from aiohttp import web
|
|
25
|
+
from binaryornot.check import is_binary
|
|
26
|
+
|
|
27
|
+
from webdiff import argparser, diff, options, util
|
|
28
|
+
from webdiff.dirdiff import make_resolved_dir
|
|
29
|
+
|
|
30
|
+
VERSION = importlib.metadata.version('webdiff')
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def determine_path():
|
|
34
|
+
"""Borrowed from wxglade.py"""
|
|
35
|
+
try:
|
|
36
|
+
root = __file__
|
|
37
|
+
if os.path.islink(root):
|
|
38
|
+
root = os.path.realpath(root)
|
|
39
|
+
return os.path.dirname(os.path.abspath(root))
|
|
40
|
+
except Exception as e:
|
|
41
|
+
print(f"I'm sorry, but something is wrong. Error: {e}")
|
|
42
|
+
print('There is no __file__ variable. Please contact the author.')
|
|
43
|
+
sys.exit()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
GIT_CONFIG = {}
|
|
47
|
+
DIFF = None
|
|
48
|
+
PORT = None
|
|
49
|
+
HOSTNAME = 'localhost'
|
|
50
|
+
DEBUG = os.environ.get('DEBUG')
|
|
51
|
+
DEBUG_DETACH = os.environ.get('DEBUG_DETACH')
|
|
52
|
+
WEBDIFF_DIR = determine_path()
|
|
53
|
+
|
|
54
|
+
if DEBUG:
|
|
55
|
+
handler = logging.StreamHandler()
|
|
56
|
+
handler.setLevel(logging.DEBUG)
|
|
57
|
+
formatter = logging.Formatter(
|
|
58
|
+
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
59
|
+
)
|
|
60
|
+
handler.setFormatter(formatter)
|
|
61
|
+
|
|
62
|
+
for logname in ['']:
|
|
63
|
+
log = logging.getLogger(logname)
|
|
64
|
+
log.setLevel(logging.DEBUG)
|
|
65
|
+
log.addHandler(handler)
|
|
66
|
+
logging.getLogger('github').setLevel(logging.ERROR)
|
|
67
|
+
logging.getLogger('binaryornot').setLevel(logging.ERROR)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
async def handle_index(request: aiohttp.web_request.Request):
|
|
71
|
+
idx = int(request.match_info.get('idx', '0'))
|
|
72
|
+
pairs = diff.get_thin_list(DIFF)
|
|
73
|
+
with open(os.path.join(WEBDIFF_DIR, 'templates/file_diff.html'), 'r') as file:
|
|
74
|
+
html = file.read()
|
|
75
|
+
html = html.replace(
|
|
76
|
+
'{{data}}',
|
|
77
|
+
json.dumps(
|
|
78
|
+
{
|
|
79
|
+
'idx': idx,
|
|
80
|
+
'has_magick': util.is_imagemagick_available(),
|
|
81
|
+
'pairs': pairs,
|
|
82
|
+
'git_config': GIT_CONFIG,
|
|
83
|
+
},
|
|
84
|
+
indent=2,
|
|
85
|
+
),
|
|
86
|
+
)
|
|
87
|
+
return web.Response(body=html, content_type='text/html', charset='utf-8')
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
async def handle_thick(request: aiohttp.web_request.Request):
|
|
91
|
+
idx = int(request.match_info.get('idx'))
|
|
92
|
+
return web.json_response(diff.get_thick_dict(DIFF[idx]))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
async def handle_get_contents(request: aiohttp.web_request.Request):
|
|
96
|
+
side = request.match_info['side']
|
|
97
|
+
form_data = await request.post()
|
|
98
|
+
path = form_data.get('path', '')
|
|
99
|
+
if not path:
|
|
100
|
+
return web.json_response({'error': 'incomplete'}, status=400)
|
|
101
|
+
should_normalize = form_data.get('normalize_json')
|
|
102
|
+
|
|
103
|
+
idx = diff.find_diff_index(DIFF, side, path)
|
|
104
|
+
if idx is None:
|
|
105
|
+
return web.json_response({'error': 'not found'}, status=400)
|
|
106
|
+
|
|
107
|
+
d = DIFF[idx]
|
|
108
|
+
abs_path = d.a_path if side == 'a' else d.b_path
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
if is_binary(abs_path):
|
|
112
|
+
size = os.path.getsize(abs_path)
|
|
113
|
+
return web.Response(text=f'Binary file ({size} bytes)')
|
|
114
|
+
else:
|
|
115
|
+
if should_normalize:
|
|
116
|
+
abs_path = util.normalize_json(abs_path)
|
|
117
|
+
return web.FileResponse(abs_path, headers={'Content-Type': 'text/plain'})
|
|
118
|
+
except Exception as e:
|
|
119
|
+
return web.json_response({'error': str(e)}, status=500)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
async def handle_diff_ops(request: aiohttp.web_request.Request):
|
|
123
|
+
idx = int(request.match_info.get('idx'))
|
|
124
|
+
payload = await request.json()
|
|
125
|
+
options = payload.get('options') or []
|
|
126
|
+
should_normalize = payload.get('normalize_json')
|
|
127
|
+
logging.debug([*payload.keys()])
|
|
128
|
+
logging.debug({**payload})
|
|
129
|
+
extra_args = GIT_CONFIG['webdiff']['extraFileDiffArgs']
|
|
130
|
+
if extra_args:
|
|
131
|
+
options += extra_args.split(' ')
|
|
132
|
+
diff_ops = [
|
|
133
|
+
dataclasses.asdict(op)
|
|
134
|
+
for op in diff.get_diff_ops(DIFF[idx], options, normalize_json=should_normalize)
|
|
135
|
+
]
|
|
136
|
+
return web.json_response(diff_ops, status=200)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
async def handle_theme(request: aiohttp.web_request.Request):
|
|
140
|
+
theme = GIT_CONFIG['webdiff']['theme']
|
|
141
|
+
theme_dir = os.path.dirname(theme)
|
|
142
|
+
theme_file = os.path.basename(theme)
|
|
143
|
+
theme_path = os.path.join(
|
|
144
|
+
WEBDIFF_DIR, 'static/css/themes', theme_dir, theme_file + '.css'
|
|
145
|
+
)
|
|
146
|
+
return web.FileResponse(theme_path)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
async def handle_favicon(request):
|
|
150
|
+
return web.FileResponse(os.path.join(WEBDIFF_DIR, 'static/img/favicon.ico'))
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
async def handle_get_image(request: aiohttp.web_request.Request):
|
|
154
|
+
side = request.match_info.get('side')
|
|
155
|
+
path = request.match_info.get('path')
|
|
156
|
+
mime_type, _ = mimetypes.guess_type(path)
|
|
157
|
+
if not mime_type or not mime_type.startswith('image/'):
|
|
158
|
+
return web.json_response({'error': 'wrong type'}, status=400)
|
|
159
|
+
|
|
160
|
+
idx = diff.find_diff_index(DIFF, side, path)
|
|
161
|
+
if idx is None:
|
|
162
|
+
return web.json_response({'error': 'not found'}, status=400)
|
|
163
|
+
|
|
164
|
+
d = DIFF[idx]
|
|
165
|
+
abs_path = d.a_path if side == 'a' else d.b_path
|
|
166
|
+
return web.FileResponse(abs_path, headers={'Content-Type': mime_type})
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
async def handle_pdiff(request: aiohttp.web_request.Request):
|
|
170
|
+
idx = int(request.match_info.get('idx'))
|
|
171
|
+
d = DIFF[idx]
|
|
172
|
+
try:
|
|
173
|
+
_, pdiff_image = util.generate_pdiff_image(d.a_path, d.b_path)
|
|
174
|
+
dilated_image_path = util.generate_dilated_pdiff_image(pdiff_image)
|
|
175
|
+
return web.FileResponse(dilated_image_path)
|
|
176
|
+
except util.ImageMagickNotAvailableError:
|
|
177
|
+
return web.Response(status=501, text='ImageMagick is not available')
|
|
178
|
+
except util.ImageMagickError as e:
|
|
179
|
+
return web.Response(status=501, text=f'ImageMagick error {e}')
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
async def handle_pdiff_bbox(request: aiohttp.web_request.Request):
|
|
183
|
+
idx = int(request.match_info.get('idx'))
|
|
184
|
+
d = DIFF[idx]
|
|
185
|
+
try:
|
|
186
|
+
_, pdiff_image = util.generate_pdiff_image(d.a_path, d.b_path)
|
|
187
|
+
bbox = util.get_pdiff_bbox(pdiff_image)
|
|
188
|
+
return web.json_response(bbox, status=200)
|
|
189
|
+
except util.ImageMagickNotAvailableError:
|
|
190
|
+
return web.json_response('ImageMagick is not available', status=501)
|
|
191
|
+
except util.ImageMagickError as e:
|
|
192
|
+
return web.json_response(f'ImageMagick error {e}', status=501)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
async def websocket_handler(request: aiohttp.web_request.Request):
|
|
196
|
+
ws = web.WebSocketResponse()
|
|
197
|
+
await ws.prepare(request)
|
|
198
|
+
|
|
199
|
+
async for msg in ws:
|
|
200
|
+
if msg.type == aiohttp.WSMsgType.TEXT:
|
|
201
|
+
await ws.send_str(msg.data + '/answer')
|
|
202
|
+
elif msg.type == aiohttp.WSMsgType.ERROR:
|
|
203
|
+
print('ws connection closed with exception %s' % ws.exception())
|
|
204
|
+
|
|
205
|
+
maybe_shutdown()
|
|
206
|
+
return ws
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@web.middleware
|
|
210
|
+
async def request_time_middleware(request, handler):
|
|
211
|
+
note_request_time()
|
|
212
|
+
response = await handler(request)
|
|
213
|
+
return response
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
app = web.Application(middlewares=[request_time_middleware])
|
|
217
|
+
app.add_routes(
|
|
218
|
+
[
|
|
219
|
+
web.get('/', handle_index),
|
|
220
|
+
web.get(r'/{idx:\d+}', handle_index),
|
|
221
|
+
web.get('/favicon.ico', handle_favicon),
|
|
222
|
+
web.get('/theme.css', handle_theme),
|
|
223
|
+
web.static('/static', os.path.join(WEBDIFF_DIR, 'static')),
|
|
224
|
+
web.get(r'/thick/{idx:\d+}', handle_thick),
|
|
225
|
+
web.post(r'/{side:a|b}/get_contents', handle_get_contents),
|
|
226
|
+
web.post(r'/diff/{idx:\d+}', handle_diff_ops),
|
|
227
|
+
# Image diffs
|
|
228
|
+
web.get(r'/{side:a|b}/image/{path:.*}', handle_get_image),
|
|
229
|
+
web.get(r'/pdiff/{idx:\d+}', handle_pdiff),
|
|
230
|
+
web.get(r'/pdiffbbox/{idx:\d+}', handle_pdiff_bbox),
|
|
231
|
+
# Websocket for detecting when the tab is closed
|
|
232
|
+
web.get('/ws', websocket_handler),
|
|
233
|
+
]
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def note_request_time():
|
|
238
|
+
global LAST_REQUEST_MS
|
|
239
|
+
LAST_REQUEST_MS = time.time() * 1000
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def open_browser():
|
|
243
|
+
global PORT
|
|
244
|
+
global HOSTNAME
|
|
245
|
+
global GIT_CONFIG
|
|
246
|
+
if not os.environ.get('WEBDIFF_NO_OPEN') and GIT_CONFIG['webdiff']['openBrowser']:
|
|
247
|
+
webbrowser.open_new_tab('http://%s:%s' % (HOSTNAME, PORT))
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def usage_and_die():
|
|
251
|
+
sys.stderr.write(argparser.USAGE)
|
|
252
|
+
sys.exit(1)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def random_port():
|
|
256
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
257
|
+
sock.bind(('localhost', 0))
|
|
258
|
+
port = sock.getsockname()[1]
|
|
259
|
+
sock.close()
|
|
260
|
+
return port
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def pick_a_port(args, webdiff_config):
|
|
264
|
+
if 'port' in args:
|
|
265
|
+
return args['port']
|
|
266
|
+
|
|
267
|
+
env_port = os.environ.get('WEBDIFF_PORT')
|
|
268
|
+
if env_port:
|
|
269
|
+
return int(env_port)
|
|
270
|
+
|
|
271
|
+
# gitconfig
|
|
272
|
+
if webdiff_config['port'] != -1:
|
|
273
|
+
return webdiff_config['port']
|
|
274
|
+
|
|
275
|
+
return random_port()
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def run_http():
|
|
279
|
+
threading.Timer(0.1, open_browser).start()
|
|
280
|
+
web.run_app(app, host=HOSTNAME, port=PORT, print=print if DEBUG else None)
|
|
281
|
+
logging.debug('http server shut down')
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def maybe_shutdown():
|
|
285
|
+
"""Wait a second for new requests, then shut down the server."""
|
|
286
|
+
last_ms = LAST_REQUEST_MS
|
|
287
|
+
|
|
288
|
+
def shutdown():
|
|
289
|
+
if LAST_REQUEST_MS <= last_ms: # subsequent requests abort shutdown
|
|
290
|
+
logging.debug('Shutting down...')
|
|
291
|
+
signal.raise_signal(signal.SIGINT)
|
|
292
|
+
else:
|
|
293
|
+
logging.debug('Received subsequent request; shutdown aborted.')
|
|
294
|
+
|
|
295
|
+
logging.debug(
|
|
296
|
+
'Received request to shut down; waiting 500ms for subsequent requests...'
|
|
297
|
+
)
|
|
298
|
+
threading.Timer(0.5, shutdown).start()
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def run():
|
|
302
|
+
global DIFF, PORT, HOSTNAME, GIT_CONFIG
|
|
303
|
+
try:
|
|
304
|
+
parsed_args = argparser.parse(sys.argv[1:], VERSION)
|
|
305
|
+
except argparser.UsageError as e:
|
|
306
|
+
sys.stderr.write('Error: %s\n\n' % e)
|
|
307
|
+
usage_and_die()
|
|
308
|
+
|
|
309
|
+
GIT_CONFIG = options.get_config()
|
|
310
|
+
WEBDIFF_CONFIG = GIT_CONFIG['webdiff']
|
|
311
|
+
DIFF = argparser.diff_for_args(parsed_args, WEBDIFF_CONFIG)
|
|
312
|
+
|
|
313
|
+
if DEBUG:
|
|
314
|
+
sys.stderr.write('Invoked as: %s\n' % sys.argv)
|
|
315
|
+
sys.stderr.write('Args: %s\n' % parsed_args)
|
|
316
|
+
sys.stderr.write('Diff: %s\n' % DIFF)
|
|
317
|
+
sys.stderr.write('GitConfig: %s\n' % GIT_CONFIG)
|
|
318
|
+
|
|
319
|
+
PORT = pick_a_port(parsed_args, WEBDIFF_CONFIG)
|
|
320
|
+
HOSTNAME = (
|
|
321
|
+
parsed_args.get('host')
|
|
322
|
+
or os.environ.get('WEBDIFF_HOST')
|
|
323
|
+
or WEBDIFF_CONFIG['host']
|
|
324
|
+
)
|
|
325
|
+
if HOSTNAME == '<hostname>':
|
|
326
|
+
_hostname = platform.node()
|
|
327
|
+
# platform.node will return empty string if it can't find the hostname
|
|
328
|
+
if not _hostname:
|
|
329
|
+
sys.stderr.write('Warning: hostname could not be determined\n')
|
|
330
|
+
else:
|
|
331
|
+
HOSTNAME = _hostname
|
|
332
|
+
|
|
333
|
+
run_in_process = os.environ.get('WEBDIFF_RUN_IN_PROCESS') or (
|
|
334
|
+
DEBUG and not DEBUG_DETACH
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
if not os.environ.get('WEBDIFF_LOGGED_MESSAGE'):
|
|
338
|
+
# Printing this in the main process gives you your prompt back more cleanly.
|
|
339
|
+
print(
|
|
340
|
+
"""Serving diffs on http://%s:%s
|
|
341
|
+
Close the browser tab when you're done to terminate the process."""
|
|
342
|
+
% (HOSTNAME, PORT)
|
|
343
|
+
)
|
|
344
|
+
os.environ['WEBDIFF_LOGGED_MESSAGE'] = '1'
|
|
345
|
+
|
|
346
|
+
if run_in_process:
|
|
347
|
+
run_http()
|
|
348
|
+
else:
|
|
349
|
+
os.environ['WEBDIFF_RUN_IN_PROCESS'] = '1'
|
|
350
|
+
os.environ['WEBDIFF_PORT'] = str(PORT)
|
|
351
|
+
if os.environ.get('WEBDIFF_FROM_GIT_DIFFTOOL'):
|
|
352
|
+
# git difftool will clean up these directories when we detach.
|
|
353
|
+
# To make them accessible to the child process, we make a (shallow) copy.
|
|
354
|
+
assert 'dirs' in parsed_args
|
|
355
|
+
dir_a, dir_b = parsed_args['dirs']
|
|
356
|
+
copied_dir_a = make_resolved_dir(dir_a)
|
|
357
|
+
copied_dir_b = make_resolved_dir(dir_b)
|
|
358
|
+
os.environ['WEBDIFF_DIR_A'] = copied_dir_a
|
|
359
|
+
os.environ['WEBDIFF_DIR_B'] = copied_dir_b
|
|
360
|
+
logging.debug(f'Copied {dir_a} -> {copied_dir_a} before detaching')
|
|
361
|
+
logging.debug(f'Copied {dir_b} -> {copied_dir_b} before detaching')
|
|
362
|
+
subprocess.Popen((sys.executable, *sys.argv))
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
if __name__ == '__main__':
|
|
366
|
+
run()
|
|
@@ -4,9 +4,7 @@ import argparse
|
|
|
4
4
|
import os
|
|
5
5
|
import re
|
|
6
6
|
|
|
7
|
-
from webdiff import dirdiff
|
|
8
|
-
from webdiff import githubdiff
|
|
9
|
-
from webdiff import github_fetcher
|
|
7
|
+
from webdiff import dirdiff, github_fetcher, githubdiff
|
|
10
8
|
from webdiff.localfilediff import LocalFileDiff
|
|
11
9
|
|
|
12
10
|
|
|
@@ -81,6 +79,12 @@ def parse(args, version=None):
|
|
|
81
79
|
|
|
82
80
|
else:
|
|
83
81
|
a, b = args.dirs
|
|
82
|
+
if os.environ.get('WEBDIFF_DIR_A') and os.environ.get('WEBDIFF_DIR_B'):
|
|
83
|
+
# This happens when you run "git webdiff" and we have to make a copy of
|
|
84
|
+
# the temp directories before we detach and git difftool cleans them up.
|
|
85
|
+
a = os.environ.get('WEBDIFF_DIR_A')
|
|
86
|
+
b = os.environ.get('WEBDIFF_DIR_B')
|
|
87
|
+
|
|
84
88
|
for x in (a, b):
|
|
85
89
|
if not os.path.exists(x):
|
|
86
90
|
raise UsageError('"%s" does not exist' % x)
|
|
@@ -29,17 +29,30 @@ def get_thin_dict(diff):
|
|
|
29
29
|
- change type (add, delete, move, change)
|
|
30
30
|
- (in the future) diffstats
|
|
31
31
|
"""
|
|
32
|
-
return {
|
|
32
|
+
return {
|
|
33
|
+
'a': diff.a,
|
|
34
|
+
'b': diff.b,
|
|
35
|
+
'type': diff.type,
|
|
36
|
+
'num_add': diff.num_add,
|
|
37
|
+
'num_delete': diff.num_delete,
|
|
38
|
+
}
|
|
33
39
|
|
|
34
40
|
|
|
35
41
|
def fast_num_lines(path: str) -> int:
|
|
36
42
|
# See https://stackoverflow.com/q/9629179/388951 for the idea to use a Unix command.
|
|
37
43
|
# Unfortunately `wc -l` ignores the last line if there is no trailing newline. So
|
|
38
44
|
# instead, see https://stackoverflow.com/a/38870057/388951
|
|
39
|
-
|
|
45
|
+
try:
|
|
46
|
+
return int(subprocess.check_output(['grep', '-c', '', path]))
|
|
47
|
+
except subprocess.CalledProcessError as e:
|
|
48
|
+
if e.returncode == 1:
|
|
49
|
+
return 0 # grep -c returns an error code if there are no matches
|
|
50
|
+
raise
|
|
40
51
|
|
|
41
52
|
|
|
42
|
-
def get_diff_ops(
|
|
53
|
+
def get_diff_ops(
|
|
54
|
+
diff: LocalFileDiff, git_diff_args=None, normalize_json=False
|
|
55
|
+
) -> List[Code]:
|
|
43
56
|
"""Run git diff on the file pair and convert the results to a sequence of codes.
|
|
44
57
|
|
|
45
58
|
git_diff_args is passed directly to git diff. It can be something like ['-w'] or
|
|
@@ -48,6 +61,10 @@ def get_diff_ops(diff: LocalFileDiff, git_diff_args=None) -> List[Code]:
|
|
|
48
61
|
# git diff --no-index doesn't follow symlinks. So we help it a bit.
|
|
49
62
|
a_path = os.path.realpath(diff.a_path) if diff.a_path else ''
|
|
50
63
|
b_path = os.path.realpath(diff.b_path) if diff.b_path else ''
|
|
64
|
+
if normalize_json:
|
|
65
|
+
a_path = a_path and util.normalize_json(a_path)
|
|
66
|
+
b_path = b_path and util.normalize_json(b_path)
|
|
67
|
+
|
|
51
68
|
if a_path and b_path:
|
|
52
69
|
num_lines = fast_num_lines(b_path)
|
|
53
70
|
args = (
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Compute the diff between two directories on local disk."""
|
|
2
2
|
|
|
3
|
-
import os
|
|
4
3
|
import logging
|
|
4
|
+
import os
|
|
5
5
|
import shutil
|
|
6
6
|
import subprocess
|
|
7
7
|
import tempfile
|
|
@@ -27,7 +27,7 @@ def contains_symlinks(dir: str):
|
|
|
27
27
|
return False
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
def make_resolved_dir(dir: str) -> str:
|
|
30
|
+
def make_resolved_dir(dir: str, follow_symlinks=False) -> str:
|
|
31
31
|
# TODO: clean up this directory
|
|
32
32
|
temp_dir = tempfile.mkdtemp(prefix='webdiff')
|
|
33
33
|
for root, dirs, files in os.walk(dir):
|
|
@@ -38,7 +38,7 @@ def make_resolved_dir(dir: str) -> str:
|
|
|
38
38
|
src_file = os.path.join(root, file_name)
|
|
39
39
|
rel = os.path.relpath(src_file, dir)
|
|
40
40
|
dst_file = os.path.join(temp_dir, rel)
|
|
41
|
-
shutil.copy(src_file, dst_file, follow_symlinks=
|
|
41
|
+
shutil.copy(src_file, dst_file, follow_symlinks=follow_symlinks)
|
|
42
42
|
return temp_dir
|
|
43
43
|
|
|
44
44
|
|
|
@@ -49,11 +49,11 @@ def gitdiff(a_dir: str, b_dir: str, webdiff_config):
|
|
|
49
49
|
cmd += ' ' + extra_args
|
|
50
50
|
a_dir_nosym = a_dir
|
|
51
51
|
if contains_symlinks(a_dir):
|
|
52
|
-
a_dir_nosym = make_resolved_dir(a_dir)
|
|
52
|
+
a_dir_nosym = make_resolved_dir(a_dir, follow_symlinks=True)
|
|
53
53
|
logging.debug(f'Inlined symlinks in left directory {a_dir} -> {a_dir_nosym}')
|
|
54
54
|
b_dir_nosym = b_dir
|
|
55
55
|
if contains_symlinks(b_dir):
|
|
56
|
-
b_dir_nosym = make_resolved_dir(b_dir)
|
|
56
|
+
b_dir_nosym = make_resolved_dir(b_dir, follow_symlinks=True)
|
|
57
57
|
logging.debug(f'Inlined symlinks in right directory {b_dir} -> {b_dir_nosym}')
|
|
58
58
|
args = cmd.split(' ') + [a_dir_nosym, b_dir_nosym]
|
|
59
59
|
logging.debug('Running git command: %s', args)
|
|
@@ -6,11 +6,11 @@ This will create symlinks or clone git repos as needed.
|
|
|
6
6
|
# Use this PR for testing to see all four types of change at once:
|
|
7
7
|
# https://github.com/danvk/test-repo/pull/2/
|
|
8
8
|
|
|
9
|
-
from collections import OrderedDict
|
|
10
9
|
import os
|
|
11
10
|
import re
|
|
12
11
|
import subprocess
|
|
13
12
|
import sys
|
|
13
|
+
from collections import OrderedDict
|
|
14
14
|
|
|
15
15
|
from github import Github, UnknownObjectException
|
|
16
16
|
|
|
@@ -138,7 +138,7 @@ def _get_github_remotes():
|
|
|
138
138
|
|
|
139
139
|
# e.g. 'origin git@github.com:danvk/expandable-image-grid.git (push)'
|
|
140
140
|
ssh_push_re = re.compile(
|
|
141
|
-
'(?P<name>[^\s]+)\s+((?P<user>[^@]+)@)?(?P<host>[^:]+)(?::(?P<path>[^\s]+))?\s\(push\)'
|
|
141
|
+
r'(?P<name>[^\s]+)\s+((?P<user>[^@]+)@)?(?P<host>[^:]+)(?::(?P<path>[^\s]+))?\s\(push\)'
|
|
142
142
|
)
|
|
143
143
|
|
|
144
144
|
# e.g. 'origin https://github.com/danvk/git-helpers.git (push)'
|
|
@@ -21,6 +21,7 @@ def run(argv=sys.argv):
|
|
|
21
21
|
if not os.environ.get('DEBUG')
|
|
22
22
|
else os.path.join(os.path.curdir, 'test.sh')
|
|
23
23
|
)
|
|
24
|
+
os.environ['WEBDIFF_FROM_GIT_DIFFTOOL'] = '1'
|
|
24
25
|
subprocess.call(f'git difftool -d -x {cmd}'.split(' ') + argv[1:])
|
|
25
26
|
except KeyboardInterrupt:
|
|
26
27
|
# Don't raise an exception to the user when sigint is received
|