diff --git a/dulwich/diffstat.py b/dulwich/diffstat.py new file mode 100755 index 00000000..16df546e --- /dev/null +++ b/dulwich/diffstat.py @@ -0,0 +1,325 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab + +# Copyright (c) 2020 Kevin B. Hendricks, Stratford Ontario Canada +# All rights reserved. +# +# This diffstat code was extracted and heavily modified from: +# +# https://github.com/techtonik/python-patch +# Under the following license: +# +# Patch utility to apply unified diffs +# Brute-force line-by-line non-recursive parsing +# +# Copyright (c) 2008-2016 anatoly techtonik +# Available under the terms of MIT license +# +# and falls under the exact same MIT license + +import sys +import re + +# only needs to detect git style diffs as this is for +# use with dulwich + +_git_header_name = re.compile(br'diff --git a/(.*) b/(.*)') + +_GIT_HEADER_START = b'diff --git a/' +_GIT_BINARY_START = b'Binary file' +_GIT_RENAMEFROM_START = b'rename from' +_GIT_RENAMETO_START = b'rename to' +_GIT_CHUNK_START = b'@@' +_GIT_ADDED_START = b'+' +_GIT_DELETED_START = b'-' +_GIT_UNCHANGED_START = b' ' + +# emulate original full Patch class by just extracting +# filename and minimal chunk added/deleted information to +# properly interface with diffstat routine + + +def _parse_patch(lines): + """An internal routine to parse a git style diff or patch to generate + diff stats + Args: + lines: list of byte strings "lines" from the diff to be parsed + Returns: A tuple (names, nametypes, counts) of three lists: + names = list of repo relative file paths + nametypes - list of booolean values indicating if file + is binary (True means binary file) + counts = list of tuples of (added, deleted) counts for that file + """ + names = [] + nametypes = [] + counts = [] + in_patch_chunk = in_git_header = binaryfile = False + currentfile = None + added = deleted = 0 + for line in lines: + if line.startswith(_GIT_HEADER_START): + if currentfile is not None: + names.append(currentfile) + nametypes.append(binaryfile) + counts.append((added, deleted)) + currentfile = _git_header_name.search(line).group(2) + binaryfile = False + added = deleted = 0 + in_git_header = True + in_patch_chunk = False + elif line.startswith(_GIT_BINARY_START) and in_git_header: + binaryfile = True + in_git_header = False + elif line.startswith(_GIT_RENAMEFROM_START) and in_git_header: + currentfile = line[12:] + elif line.startswith(_GIT_RENAMETO_START) and in_git_header: + currentfile += b' => %s' % line[10:] + elif line.startswith(_GIT_CHUNK_START) and \ + (in_patch_chunk or in_git_header): + in_patch_chunk = True + in_git_header = False + elif line.startswith(_GIT_ADDED_START) and in_patch_chunk: + added += 1 + elif line.startswith(_GIT_DELETED_START) and in_patch_chunk: + deleted += 1 + elif not line.startswith(_GIT_UNCHANGED_START) and in_patch_chunk: + in_patch_chunk = False + # handle end of input + if currentfile is not None: + names.append(currentfile) + nametypes.append(binaryfile) + counts.append((added, deleted)) + return names, nametypes, counts + + +# note must all done using bytes not string because on linux filenames +# may not be encodable even to utf-8 +def diffstat(lines, max_width=80): + """Generate summary statistics from a git style diff ala + (git diff tag1 tag2 --stat) + Args: + lines: list of byte string "lines" from the diff to be parsed + max_width: maximum line length for generating the summary + statistics (default 80) + Returns: A byte string that lists the changed files with change + counts and histogram + """ + names, nametypes, counts = _parse_patch(lines) + insert = [] + delete = [] + namelen = 0 + maxdiff = 0 # max changes for any file used for histogram width calc + for i, filename in enumerate(names): + i, d = counts[i] + insert.append(i) + delete.append(d) + namelen = max(namelen, len(filename)) + maxdiff = max(maxdiff, i+d) + output = b'' + statlen = len(str(maxdiff)) # stats column width + for i, n in enumerate(names): + binaryfile = nametypes[i] + # %-19s | %-4d %s + # note b'%d' % namelen is not supported until Python 3.5 + # To convert an int to a format width specifier for byte + # strings use str(namelen).encode('ascii') + format = b' %-' + str(namelen).encode('ascii') + \ + b's | %' + str(statlen).encode('ascii') + b's %s\n' + binformat = b' %-' + str(namelen).encode('ascii') + b's | %s\n' + if not binaryfile: + hist = b'' + # -- calculating histogram -- + width = len(format % (b'', b'', b'')) + histwidth = max(2, max_width - width) + if maxdiff < histwidth: + hist = b'+'*insert[i] + b'-'*delete[i] + else: + iratio = (float(insert[i]) / maxdiff) * histwidth + dratio = (float(delete[i]) / maxdiff) * histwidth + iwidth = dwidth = 0 + # make sure every entry that had actual insertions gets + # at least one + + if insert[i] > 0: + iwidth = int(iratio) + if iwidth == 0 and 0 < iratio < 1: + iwidth = 1 + # make sure every entry that had actual deletions gets + # at least one - + if delete[i] > 0: + dwidth = int(dratio) + if dwidth == 0 and 0 < dratio < 1: + dwidth = 1 + hist = b'+'*int(iwidth) + b'-'*int(dwidth) + output += (format % (bytes(names[i]), + str(insert[i] + delete[i]).encode('ascii'), + hist)) + else: + output += (binformat % (bytes(names[i]), b'Bin')) + + output += (b' %d files changed, %d insertions(+), %d deletions(-)' + % (len(names), sum(insert), sum(delete))) + return output + + +def main(): + argv = sys.argv + # allow diffstat.py to also be used from the comand line + if len(sys.argv) > 1: + diffpath = argv[1] + data = b'' + with open(diffpath, 'rb') as f: + data = f.read() + lines = data.split(b'\n') + result = diffstat(lines) + print(result.decode('utf-8')) + return 0 + + # if no path argument to a diff file is passed in, run + # a self test. The test case includes tricky things like + # a diff of diff, binary files, renames with futher changes + # added files and removed files. + # All extracted from Sigil-Ebook/Sigil's github repo with + # full permission to use under this license. + selftest = b""" +diff --git a/docs/qt512.7_remove_bad_workaround.patch b/docs/qt512.7_remove_bad_workaround.patch +new file mode 100644 +index 00000000..64e34192 +--- /dev/null ++++ b/docs/qt512.7_remove_bad_workaround.patch +@@ -0,0 +1,15 @@ ++--- qtbase/src/gui/kernel/qwindow.cpp.orig 2019-12-12 09:15:59.000000000 -0500 +++++ qtbase/src/gui/kernel/qwindow.cpp 2020-01-10 10:36:53.000000000 -0500 ++@@ -218,12 +218,6 @@ ++ QGuiApplicationPrivate::window_list.removeAll(this); ++ if (!QGuiApplicationPrivate::is_app_closing) ++ QGuiApplicationPrivate::instance()->modalWindowList.removeOne(this); ++- ++- // focus_window is normally cleared in destroy(), but the window may in ++- // some cases end up becoming the focus window again. Clear it again ++- // here as a workaround. See QTBUG-75326. ++- if (QGuiApplicationPrivate::focus_window == this) ++- QGuiApplicationPrivate::focus_window = 0; ++ } ++ ++ void QWindowPrivate::init(QScreen *targetScreen) +diff --git a/docs/testplugin_v017.zip b/docs/testplugin_v017.zip +new file mode 100644 +index 00000000..a4cf4c4c +Binary files /dev/null and b/docs/testplugin_v017.zip differ +diff --git a/ci_scripts/macgddeploy.py b/ci_scripts/gddeploy.py +similarity index 73% +rename from ci_scripts/macgddeploy.py +rename to ci_scripts/gddeploy.py +index a512d075..f9dacd33 100644 +--- a/ci_scripts/macgddeploy.py ++++ b/ci_scripts/gddeploy.py +@@ -1,19 +1,32 @@ + #!/usr/bin/env python3 + + import os ++import sys + import subprocess + import datetime + import shutil ++import glob + + gparent = os.path.expandvars('$GDRIVE_DIR') + grefresh_token = os.path.expandvars('$GDRIVE_REFRESH_TOKEN') + +-travis_branch = os.path.expandvars('$TRAVIS_BRANCH') +-travis_commit = os.path.expandvars('$TRAVIS_COMMIT') +-travis_build_number = os.path.expandvars('$TRAVIS_BUILD_NUMBER') ++if sys.platform.lower().startswith('darwin'): ++ travis_branch = os.path.expandvars('$TRAVIS_BRANCH') ++ travis_commit = os.path.expandvars('$TRAVIS_COMMIT') ++ travis_build_number = os.path.expandvars('$TRAVIS_BUILD_NUMBER') ++ ++ origfilename = './bin/Sigil.tar.xz' ++ newfilename = './bin/Sigil-{}-{}-build_num-{}.tar.xz'.format(travis_branch, travis_commit[:7],travis_build_numbe\ +r) ++else: ++ appveyor_branch = os.path.expandvars('$APPVEYOR_REPO_BRANCH') ++ appveyor_commit = os.path.expandvars('$APPVEYOR_REPO_COMMIT') ++ appveyor_build_number = os.path.expandvars('$APPVEYOR_BUILD_NUMBER') ++ names = glob.glob('.\\installer\\Sigil-*-Setup.exe') ++ if not names: ++ exit(1) ++ origfilename = names[0] ++ newfilename = '.\\installer\\Sigil-{}-{}-build_num-{}-Setup.exe'.format(appveyor_branch, appveyor_commit[:7], ap\ +pveyor_build_number) + +-origfilename = './bin/Sigil.tar.xz' +-newfilename = './bin/Sigil-{}-{}-build_num-{}.tar.xz'.format(travis_branch, travis_commit[:7],travis_build_number) + shutil.copy2(origfilename, newfilename) + + folder_name = datetime.date.today() +diff --git a/docs/qt512.6_backport_009abcd_fix.patch b/docs/qt512.6_backport_009abcd_fix.patch +deleted file mode 100644 +index f4724347..00000000 +--- a/docs/qt512.6_backport_009abcd_fix.patch ++++ /dev/null +@@ -1,26 +0,0 @@ +---- qtbase/src/widgets/kernel/qwidget.cpp.orig 2019-11-08 10:57:07.000000000 -0500 +-+++ qtbase/src/widgets/kernel/qwidget.cpp 2019-12-11 12:32:24.000000000 -0500 +-@@ -8934,6 +8934,23 @@ +- } +- } +- switch (event->type()) { +-+ case QEvent::PlatformSurface: { +-+ // Sync up QWidget's view of whether or not the widget has been created +-+ switch (static_cast(event)->surfaceEventType()) { +-+ case QPlatformSurfaceEvent::SurfaceCreated: +-+ if (!testAttribute(Qt::WA_WState_Created)) +-+ create(); +-+ break; +-+ case QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed: +-+ if (testAttribute(Qt::WA_WState_Created)) { +-+ // Child windows have already been destroyed by QWindow, +-+ // so we skip them here. +-+ destroy(false, false); +-+ } +-+ break; +-+ } +-+ break; +-+ } +- case QEvent::MouseMove: +- mouseMoveEvent((QMouseEvent*)event); +- break; +diff --git a/docs/Building_Sigil_On_MacOSX.txt b/docs/Building_Sigil_On_MacOSX.txt +index 3b41fd80..64914c78 100644 +--- a/docs/Building_Sigil_On_MacOSX.txt ++++ b/docs/Building_Sigil_On_MacOSX.txt +@@ -113,7 +113,7 @@ install_name_tool -add_rpath @loader_path/../../Frameworks ./bin/Sigil.app/Content + + # To test if the newly bundled python 3 version of Sigil is working properly ypou can do the following: + +-1. download testplugin_v014.zip from https://github.com/Sigil-Ebook/Sigil/tree/master/docs ++1. download testplugin_v017.zip from https://github.com/Sigil-Ebook/Sigil/tree/master/docs + 2. open Sigil.app to the normal nearly blank template epub it generates when opened + 3. use Plugins->Manage Plugins menu and make sure the "Use Bundled Python" checkbox is checked + 4. use the "Add Plugin" button to navigate to and add testplugin.zip and then hit "Okay" to exit the Manage Plugins Dialog +""" # noqa: E501 W293 + + testoutput = b""" docs/qt512.7_remove_bad_workaround.patch | 15 ++++++++++++ + docs/testplugin_v017.zip | Bin + ci_scripts/macgddeploy.py => ci_scripts/gddeploy.py | 0 + docs/qt512.6_backport_009abcd_fix.patch | 26 --------------------- + docs/Building_Sigil_On_MacOSX.txt | 2 +- + 5 files changed, 16 insertions(+), 27 deletions(-)""" # noqa: W291 + + # return 0 on success otherwise return -1 + result = diffstat(selftest.split(b'\n')) + if result == testoutput: + print("self test passed") + return 0 + print("self test failed") + print("Received:") + print(result.decode('utf-8')) + print("Expected:") + print(testoutput.decode('utf-8')) + return -1 + + +if __name__ == '__main__': + sys.exit(main())