Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-svneverever for openSUSE:Factory checked in at 2022-10-18 12:45:15 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-svneverever (Old) and /work/SRC/openSUSE:Factory/.python-svneverever.new.2275 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-svneverever" Tue Oct 18 12:45:15 2022 rev:3 rq:1029588 version:1.7.1 Changes: -------- --- /work/SRC/openSUSE:Factory/python-svneverever/python-svneverever.changes 2020-01-17 16:03:55.068398339 +0100 +++ /work/SRC/openSUSE:Factory/.python-svneverever.new.2275/python-svneverever.changes 2022-10-18 12:45:40.169815206 +0200 @@ -1,0 +2,7 @@ +Mon Oct 17 13:44:03 UTC 2022 - pgaj...@suse.com + +- version update to 1.7.1 + * no upstream changelog +- python-six is not required + +------------------------------------------------------------------- Old: ---- v1.4.2.tar.gz New: ---- v1.7.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-svneverever.spec ++++++ --- /var/tmp/diff_new_pack.RzfpDb/_old 2022-10-18 12:45:40.577816135 +0200 +++ /var/tmp/diff_new_pack.RzfpDb/_new 2022-10-18 12:45:40.581816144 +0200 @@ -21,17 +21,15 @@ %define oldpython python %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-svneverever -Version: 1.4.2 +Version: 1.7.1 Release: 0 Summary: Tool collecting path entries across SVN history License: GPL-3.0-only URL: https://github.com/hartwork/svneverever Source: https://github.com/hartwork/svneverever/archive/v%{version}.tar.gz BuildRequires: %{python_module setuptools} -BuildRequires: %{python_module six} BuildRequires: fdupes BuildRequires: python-rpm-macros -Requires: python-six Requires: subversion Requires(post): update-alternatives Requires(postun): update-alternatives @@ -62,7 +60,7 @@ %python_uninstall_alternative %{mod_name} %files %{python_files} -%doc README.asciidoc +%doc README.md %python_alternative %{_bindir}/%{mod_name} %{python_sitelib}/%{mod_name}-%{version}-*.egg-info %{python_sitelib}/%{mod_name}/ ++++++ v1.4.2.tar.gz -> v1.7.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/svneverever-1.4.2/.pre-commit-config.yaml new/svneverever-1.7.1/.pre-commit-config.yaml --- old/svneverever-1.4.2/.pre-commit-config.yaml 2019-07-16 23:47:25.000000000 +0200 +++ new/svneverever-1.7.1/.pre-commit-config.yaml 2021-07-09 00:50:45.000000000 +0200 @@ -1,11 +1,11 @@ repos: - repo: https://github.com/asottile/pyupgrade - rev: v1.19.0 + rev: v2.19.4 hooks: - id: pyupgrade - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.2.3 + rev: v2.5.0 hooks: - id: check-merge-conflict - id: end-of-file-fixer @@ -13,7 +13,7 @@ - id: trailing-whitespace - repo: https://github.com/pre-commit/mirrors-isort - rev: v4.3.20 + rev: v5.9.1 hooks: - id: isort exclude: '^setup\.py$' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/svneverever-1.4.2/Makefile new/svneverever-1.7.1/Makefile --- old/svneverever-1.4.2/Makefile 2019-07-16 23:47:25.000000000 +0200 +++ new/svneverever-1.7.1/Makefile 2021-07-09 00:50:45.000000000 +0200 @@ -2,7 +2,7 @@ # Licensed under GPL v3 or later DESTDIR = / -PYTHON = python +PYTHON = python3 all: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/svneverever-1.4.2/README.asciidoc new/svneverever-1.7.1/README.asciidoc --- old/svneverever-1.4.2/README.asciidoc 2019-07-16 23:47:25.000000000 +0200 +++ new/svneverever-1.7.1/README.asciidoc 1970-01-01 01:00:00.000000000 +0100 @@ -1,177 +0,0 @@ -About ------ -*svneverever* helps you inspect the structure of a SVN repository -and the changes made to it over time. -Its most common use probably is in combination with -https://github.com/svn-all-fast-export/svn2git[svn-all-fast-export] -(or "KDE's svn2git" if you wish). - - -Example -------- -In the following shell session I am first dumping the SVN history of -http://bs2b.sourceforge.net/[headphone effect library bs2b] -using http://rsvndump.sourceforge.net/[rsvndump]. -(I could pass `svn://svn.code.sf.net/p/bs2b/code/` to *svneverever* directly -but it would mean to download all history for each run to *svneverever*.) - ------------------------------------------------------------------------------------------ -# svnadmin create bs2b_svn_store - -# time sh -c 'rsvndump svn://svn.code.sf.net/p/bs2b/code/ | svnadmin load bs2b_svn_store/' -[..] -real 2m54.403s -user 0m1.003s -sys 0m1.300s ------------------------------------------------------------------------------------------ - -After dumping the SVN history, I make *svneverever* show a tree of -all directories in there ever ever. - ------------------------------------------------------------------------------------------ -# svneverever --no-progress bs2b_svn_store/ -Analyzing 175 revisions... - -( 1; 175) /branches -( 66; 76) /libbs2b-config-header - [..] -( 1; 175) /tags -(109; 175) /description - [..] -( 46; 175) /libbs2b - [..] -( 28; 46) /libbs2b-2.2.1 - [..] -( 31; 175) /plugins - [..] -( 1; 175) /trunk -( 80; 175) /description -( 80; 175) /img -( 2; 175) /libbs2b -( 2; 175) /doc -( 2; 80) /img -( 2; 6) /src -( 4; 175) /m4 -( 2; 175) /src -( 2; 175) /win32 -( 2; 175) /bs2bconvert -( 2; 175) /bs2bstream -( 2; 175) /examples -( 2; 175) /sndfile -( 2; 175) /plugins -( 38; 175) /audacious -( 38; 175) /m4 -( 38; 175) /src -( 24; 175) /foobar2000 -(143; 175) /ladspa -(144; 175) /m4 -(143; 175) /src -( 39; 175) /qmmp -( 2; 175) /vst -( 2; 175) /src -( 2; 175) /resources -( 2; 175) /win32 -(117; 175) /winamp -( 2; 175) /xmms -( 12; 175) /m4 -( 12; 175) /src ------------------------------------------------------------------------------------------ - -The ranges on the left indicate -at what revision folders appeared first and got deleted latest. -You can see a few things in this very output: - - * That a branch called "libbs2b-config-header" got deleted - at revision 76. - - * In which order bs2b plug-ins appeared over time. - - * That tag "libbs2b-2.2.1" was deleted at the same revision that - a tag folder "libbs2b" appeared. - -Let's see if that tag was _actually_ moved into that tag subfolder. Yes it was: - ------------------------------------------------------------------------------------------ -# svneverever --no-progress --tags --flatten bs2b_svn_store/ | grep '2.2.1$' -Analyzing 175 revisions... - -(110; 175) /tags/description/2.2.1 -( 47; 175) /tags/libbs2b/2.2.1 -( 28; 46) /tags/libbs2b-2.2.1 ------------------------------------------------------------------------------------------ - -Next I have a look at who the committers where, when they joined or left -and how many commits the did (though that last number is of limited value). - ------------------------------------------------------------------------------------------ -# svneverever --no-progress --committers bs2b_svn_store/ -Analyzing 175 revisions... - - 81 ( 1; 174) boris_mikhaylov - 94 ( 4; 175) hartwork ------------------------------------------------------------------------------------------ - - -Installation ------------- -If your Linux distribution of choice does not come with a package for *svneverever* yet, -you can install *svneverever* using pip from PyPI ------------------------------------------------------------------------------ -# pip install svneverever ------------------------------------------------------------------------------ - -or from a Git clone: ------------------------------------------------------------------------------ -# git clone https://github.com/hartwork/svneverever.git -# cd svneverever -# python setup.py install --user -# echo 'export PATH="${HOME}/.local/bin:${PATH}"' >> ~/.bashrc ------------------------------------------------------------------------------ - -Besides Python 2.7/3.5/3.6, *svneverever*'s only dependency is -http://pysvn.tigris.org/project_downloads.html[pysvn]. - - -Usage ------ ------------------------------------------------------------------------------ -# svneverever --help -usage: svneverever [-h] [--version] [--committers] [--no-numbers] - [--no-progress] [--non-interactive] [--tags] [--branches] - [--no-dots] [--depth DEPTH] [--flatten] - [--unknown-committer NAME] - REPOSITORY - -Collects path entries across SVN history - -positional arguments: - REPOSITORY Path or URI to SVN repository - -optional arguments: - -h, --help show this help message and exit - --version show program's version number and exit - -mode selection arguments: - --committers Collect committer names instead of path information - (default: disabled) - -common arguments: - --no-numbers Hide numbers, e.g. revision ranges (default: disabled) - --no-progress Hide progress bar (default: disabled) - --non-interactive Will not ask for input (e.g. login credentials) if - required (default: ask if required) - -path tree mode arguments: - --tags Show content of tag folders (default: disabled) - --branches Show content of branch folders (default: disabled) - --no-dots Hide "[..]" omission marker (default: disabled) - --depth DEPTH Maximum depth to print (starting at 1) - --flatten Flatten tree (default: disabled) - -committer mode arguments: - --unknown-committer NAME - Committer name to use for commits without a proper - svn:author property (default: "<unknown>") - -Please report bugs at https://github.com/hartwork/svneverever. Thank you! ------------------------------------------------------------------------------ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/svneverever-1.4.2/README.md new/svneverever-1.7.1/README.md --- old/svneverever-1.4.2/README.md 1970-01-01 01:00:00.000000000 +0100 +++ new/svneverever-1.7.1/README.md 2021-07-09 00:50:45.000000000 +0200 @@ -0,0 +1,182 @@ +# About +**svneverever** helps you inspect the structure of a SVN repository and the changes made to it over time. Its most common use is in combination with [svn-all-fast-export](https://github.com/svn-all-fast-export/svn2git) (or "KDE's svn2git" if you wish). + + +# Installation + +## Dependencies +**svneverever** requires Python 3.6 or higher +and [PySVN](https://pysvn.sourceforge.io/) +(the one on SourceForge, not the one on PyPI). +If you want to install **svneverever** with **pip** you need a few additional packages. +For Debian/Ubuntu the following should work: + +```console +# sudo apt install python3-svn python3-pip +``` + +## System package manager +[Some distributions](https://repology.org/projects/?search=svneverever) offer a native package for **svneverever**: +- [Arch AUR](https://aur.archlinux.org/packages/python-svneverever/) +- [Funtoo](https://github.com/funtoo/dev-kit/tree/1.4-release/dev-vcs/svneverever) +- [Gentoo](https://packages.gentoo.org/packages/dev-vcs/svneverever) +- [openSUSE](https://software.opensuse.org/package/python-svneverever) + +## pip +Install with pip as user to avoid messes with Python system files. +```console +# pip install --user svneverever +``` + +## From source +```console +# git clone https://github.com/hartwork/svneverever.git +# cd svneverever +# python3 setup.py install --user +# echo 'export PATH="${HOME}/.local/bin:${PATH}"' >> ~/.bashrc +``` + +# Usage +**svneverever** needs the "server-side" of the repository with full history. There are two ways to obtain this. Let's take the SVN history of [headphone effect library bs2b](http://bs2b.sourceforge.net/) as an example. + +The first way is to directly point **svneverever** to the server `svneverever svn://svn.code.sf.net/p/bs2b/code/`. However this is slow and it puts a lot of stress on the server. + +The second, and recommended, method is first downloading the history with **rsvndump**. For this method we first need to use `svnadmin` to create an empty repository and then load the output of **rsvndump** into it. This can be done in the following way: + +```console +# svnadmin create bs2b_svn_store + +# time sh -c 'rsvndump svn://svn.code.sf.net/p/bs2b/code/ | svnadmin load bs2b_svn_store/' +[..] +real 2m54.403s +user 0m1.003s +sys 0m1.300s +``` + +The output can now be obtained by pointing **svneverver** to the svn store directory `bs2b_svn_store`. + +```console +# svneverever --no-progress bs2b_svn_store/ +``` + +## Analyzing the output +The output of the direct method or the **rsvndump** method will be the same and looks like this: + +```console +Analyzing 175 revisions... + +( 1; 175) /branches +( 66; 76) /libbs2b-config-header + [..] +( 1; 175) /tags +(109; 175) /description + [..] +( 46; 175) /libbs2b + [..] +( 28; 46) /libbs2b-2.2.1 + [..] +( 31; 175) /plugins + [..] +( 1; 175) /trunk +( 80; 175) /description +( 80; 175) /img +( 2; 175) /libbs2b +( 2; 175) /doc +( 2; 80) /img +( 2; 6) /src +( 4; 175) /m4 +( 2; 175) /src +( 2; 175) /win32 +( 2; 175) /bs2bconvert +( 2; 175) /bs2bstream +( 2; 175) /examples +( 2; 175) /sndfile +( 2; 175) /plugins +( 38; 175) /audacious +( 38; 175) /m4 +( 38; 175) /src +( 24; 175) /foobar2000 +(143; 175) /ladspa +(144; 175) /m4 +(143; 175) /src +( 39; 175) /qmmp +( 2; 175) /vst +( 2; 175) /src +( 2; 175) /resources +( 2; 175) /win32 +(117; 175) /winamp +( 2; 175) /xmms +( 12; 175) /m4 +( 12; 175) /src +``` + +The ranges on the left indicate at what revision folders appeared first and got deleted latest. + +You can see a few things in this output: +* That a branch called `libbs2b-config-header` got deleted at revision 76. +* In which order plug-ins in `plugins` appeared over time. +* That tag `libbs2b-2.2.1` was deleted at the same revision that a tag folder `libbs2b` appeared. + +The last item we can verify to see if it was _actually_ moved into that tag subfolder. + +```console +# svneverever --no-progress --tags --flatten bs2b_svn_store/ | grep '2.2.1$' +Analyzing 175 revisions... + +(110; 175) /tags/description/2.2.1 +( 47; 175) /tags/libbs2b/2.2.1 +( 28; 46) /tags/libbs2b-2.2.1 +``` + +Next I have a look at who the committers where, when they joined or left and how many commits the did (though that last number is of limited value). This can help to write an identity map for svn2git. + +```console +# svneverever --no-progress --committers bs2b_svn_store/ +Analyzing 175 revisions... + + 81 ( 1; 174) boris_mikhaylov + 94 ( 4; 175) hartwork +``` + +# `--help` output +```console +# svneverever --help +usage: svneverever [-h] [--version] [--committers] [--no-numbers] + [--no-progress] [--non-interactive] [--tags] [--branches] + [--no-dots] [--depth DEPTH] [--flatten] + [--unknown-committer NAME] + REPOSITORY + +Collects path entries across SVN history + +positional arguments: + REPOSITORY Path or URI to SVN repository + +optional arguments: + -h, --help show this help message and exit + --version show program's version number and exit + +mode selection arguments: + --committers Collect committer names instead of path information + (default: disabled) + +common arguments: + --no-numbers Hide numbers, e.g. revision ranges (default: disabled) + --no-progress Hide progress bar (default: disabled) + --non-interactive Will not ask for input (e.g. login credentials) if + required (default: ask if required) + +path tree mode arguments: + --tags Show content of tag folders (default: disabled) + --branches Show content of branch folders (default: disabled) + --no-dots Hide "[..]" omission marker (default: disabled) + --depth DEPTH Maximum depth to print (starting at 1) + --flatten Flatten tree (default: disabled) + +committer mode arguments: + --unknown-committer NAME + Committer name to use for commits without a proper + svn:author property (default: "<unknown>") + +Please report bugs at https://github.com/hartwork/svneverever. Thank you! +``` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/svneverever-1.4.2/modules/svneverever/__main__.py new/svneverever-1.7.1/modules/svneverever/__main__.py --- old/svneverever-1.4.2/modules/svneverever/__main__.py 2019-07-16 23:47:25.000000000 +0200 +++ new/svneverever-1.7.1/modules/svneverever/__main__.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,437 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright (C) 2010-2019 Sebastian Pipping <sebast...@pipping.org> -# Copyright (C) 2011 Wouter Haffmans <wou...@boxplosive.nl> -# Copyright (C) 2019 Kevin Lane <kevin.lan...@outlook.com> -# Licensed under GPL v3 or later -# -from __future__ import print_function - -import getpass -import math -import os -import signal -import sys -import time -from collections import namedtuple - -import pysvn -import six -from six.moves import xrange -from six.moves.urllib.parse import quote as urllib_parse_quote - -try: - import argparse -except ImportError: - print("ERROR: You need Python 2.7+ unless you have module argparse " - "(package dev-python/argparse on Gentoo) installed independently.", - file=sys.stderr) - sys.exit(1) - - -_EPILOG = """\ -Please report bugs at https://github.com/hartwork/svneverever. Thank you! -""" - - -_OsTerminalSize = namedtuple('_OsTerminalSize', ['columns', 'lines']) - - -def _os_get_terminal_size_pre_3_3(fd=0): - import fcntl - import struct - import termios - - lines, columns, _ph, _pw = struct.unpack('HHHH', ( - fcntl.ioctl(fd, termios.TIOCGWINSZ, struct.pack('HHHH', 0, 0, 0, 0)))) - - return _OsTerminalSize(columns=columns, lines=lines) - - -def _get_terminal_size_or_default(): - try: - return int(os.environ['COLUMNS']) - except (KeyError, ValueError): - pass - - if sys.version_info >= (3, 3): - query_fd = os.get_terminal_size - else: - query_fd = _os_get_terminal_size_pre_3_3 - - for fd in (0, 1, 2): - try: - return query_fd(fd) - except Exception: - pass - - try: - fd = os.open(os.ctermid(), os.O_RDONLY) - try: - return query_fd(fd) - finally: - os.close(fd) - except Exception: - pass - - return _OsTerminalSize(columns=80, lines=24) - - -def _for_print(text): - if sys.version_info >= (3, ): - return text - - return text.encode(sys.stdout.encoding or 'UTF-8', 'replace') - - -def dump_tree(t, revision_digits, latest_revision, config, - level=0, branch_level=-3, tag_level=-3, parent_dir=''): - def indent_print(line_start, text): - if config.flat_tree: - level_text = parent_dir - else: - level_text = ' '*(4*level) - if config.show_numbers: - print('{} {}{}'.format(line_start, level_text, _for_print(text))) - else: - print('{}{}'.format(level_text, _for_print(text))) - - items = ((k, v) for k, v in t.items() if k) - - if ((branch_level + 2 == level) and not config.show_branches) \ - or ((tag_level + 2 == level) and not config.show_tags) \ - or level >= config.max_depth: - if items and config.show_dots: - line_start = ' '*(1 + revision_digits + 2 + revision_digits + 1) - if config.flat_tree: - indent_print(line_start, '/[..]') - else: - indent_print(line_start, '[..]') - return - - for k, (added_on_rev, last_deleted_on_rev, children) in sorted(items): - format = '(%%%dd; %%%dd)' % (revision_digits, revision_digits) - if last_deleted_on_rev is not None: - last_seen_rev = last_deleted_on_rev - 1 - else: - last_seen_rev = latest_revision - visual_rev = format % (added_on_rev, last_seen_rev) - - indent_print(visual_rev, '/%s' % k) - - bl = branch_level - tl = tag_level - if k == 'branches': - bl = level - elif k == 'tags': - tl = level - dump_tree(children, revision_digits, latest_revision, config, - level=level + 1, branch_level=bl, tag_level=tl, - parent_dir='{}/{}'.format(parent_dir, k)) - - -def dump_nick_stats(nick_stats, revision_digits, config): - if config.show_numbers: - format = "%%%dd (%%%dd; %%%dd) %%s" % (revision_digits, - revision_digits, - revision_digits) - for nick, (first_commit_rev, last_commit_rev, commit_count) \ - in sorted(nick_stats.items()): - print(format % (commit_count, first_commit_rev, last_commit_rev, - _for_print(nick))) - else: - for nick, (first_commit_rev, last_commit_rev, commit_count) \ - in sorted(nick_stats.items()): - print(_for_print(nick)) - - -def ensure_uri(text): - import re - svn_uri_detector = re.compile('^[A-Za-z+]+://') - if svn_uri_detector.search(text): - return text - else: - import os - abspath = os.path.abspath(text) - return 'file://%s' % urllib_parse_quote(abspath) - - -def digit_count(n): - if n == 0: - return 1 - assert(n > 0) - return int(math.floor(math.log10(n)) + 1) - - -def hms(seconds): - seconds = math.ceil(seconds) - h = int(seconds / 3600) - seconds = seconds - h*3600 - m = int(seconds / 60) - seconds = seconds - m*60 - return h, m, seconds - - -def make_progress_bar(percent, width, seconds_taken, seconds_expected): - other_len = (6 + 1) + 2 + (1 + 8 + 1 + 9 + 1) + 3 + 1 - assert(width > 0) - bar_content_len = width - other_len - assert(bar_content_len >= 0) - fill_len = int(percent * bar_content_len / 100) - open_len = bar_content_len - fill_len - seconds_remaining = seconds_expected - seconds_taken - hr, mr, sr = hms(seconds_remaining) - if hr > 99: - hr = 99 - return ('%6.2f%% (%02d:%02d:%02d remaining) [%s%s]' - % (percent, hr, mr, sr, '#'*fill_len, ' '*open_len)) - - -def command_line(): - from svneverever.version import VERSION_STR - parser = argparse.ArgumentParser( - prog='svneverever', - description='Collects path entries across SVN history', - epilog=_EPILOG, - formatter_class=argparse.RawDescriptionHelpFormatter, - ) - parser.add_argument( - '--version', - action='version', version='%(prog)s ' + VERSION_STR) - parser.add_argument( - 'repo_uri', - metavar='REPOSITORY', action='store', - help='Path or URI to SVN repository') - - modes = parser.add_argument_group('mode selection arguments') - modes.add_argument( - '--committers', - dest='nick_stat_mode', action='store_true', default=False, - help='Collect committer names instead of path information ' - '(default: disabled)') - - common = parser.add_argument_group('common arguments') - common.add_argument( - '--no-numbers', - dest='show_numbers', action='store_false', default=True, - help='Hide numbers, e.g. revision ranges (default: disabled)') - common.add_argument( - '--no-progress', - dest='show_progress', action='store_false', default=True, - help='Hide progress bar (default: disabled)') - common.add_argument( - '--non-interactive', - dest='interactive', action='store_false', default=True, - help='Will not ask for input (e.g. login credentials) if required' - ' (default: ask if required)') - - path_tree_mode = parser.add_argument_group('path tree mode arguments') - path_tree_mode.add_argument( - '--tags', - dest='show_tags', action='store_true', default=False, - help='Show content of tag folders (default: disabled)') - path_tree_mode.add_argument( - '--branches', - dest='show_branches', action='store_true', default=False, - help='Show content of branch folders (default: disabled)') - path_tree_mode.add_argument( - '--no-dots', - dest='show_dots', action='store_false', default=True, - help='Hide "[..]" omission marker (default: disabled)') - path_tree_mode.add_argument( - '--depth', - dest='max_depth', metavar='DEPTH', action='store', - type=int, default=-1, - help='Maximum depth to print (starting at 1)') - path_tree_mode.add_argument( - '--flatten', - dest='flat_tree', action='store_true', default=False, - help='Flatten tree (default: disabled)') - - committer_mode = parser.add_argument_group('committer mode arguments') - committer_mode.add_argument( - '--unknown-committer', - dest='unknown_committer_name', metavar='NAME', default='<unknown>', - help='Committer name to use for commits' - ' without a proper svn:author property (default: "%(default)s")') - - args = parser.parse_args() - - args.repo_uri = ensure_uri(args.repo_uri) - if args.max_depth < 1: - args.max_depth = six.MAXSIZE - - return args - - -def _login(realm, username, may_save, _tries): - if _tries > 0: - print('ERROR: Credentials not accepted by SVN, please try again.', - file=sys.stderr) - - try: - if username: - print('Username: {} (as requested by SVN)'.format(username), - file=sys.stderr) - else: - print('Username: ', end='', file=sys.stderr) - username = six.moves.input('') - password = getpass.getpass('Password: ') - print(file=sys.stderr) - return True, username, password, False - except (KeyboardInterrupt, EOFError): - print(file=sys.stderr) - print('Operation cancelled.', file=sys.stderr) - sys.exit(128 + signal.SIGINT) - - -def _create_login_callback(): - tries = 0 - - def login_with_try_counter(*args, **kvargs): - nonlocal tries - - kvargs['_tries'] = tries - try: - return _login(*args, **kvargs) - finally: - tries += 1 - - return login_with_try_counter - - -def main(): - args = command_line() - - # Build tree from repo - client = pysvn.Client() - if args.interactive: - client.callback_get_login = _create_login_callback() - tree = dict() - try: - latest_revision = client.info2( - args.repo_uri, recurse=False)[0][1]['last_changed_rev'].number - except (pysvn.ClientError) as e: - if str(e) == 'callback_get_login required': - print('ERROR: SVN Repository requires login credentials' - '. Please run without --non-interactive switch.', - file=sys.stderr) - else: - print('ERROR: %s' % str(e), file=sys.stderr) - sys.exit(1) - - start_time = time.time() - print('Analyzing %d revisions...' % latest_revision, file=sys.stderr) - width = _get_terminal_size_or_default().columns - - def indicate_progress(rev, before_work=False): - percent = rev * 100.0 / latest_revision - seconds_taken = time.time() - start_time - seconds_expected = seconds_taken / float(rev) * latest_revision - if (rev == latest_revision) and not before_work: - percent = 100 - seconds_expected = seconds_taken - print('\r' + make_progress_bar(percent, width, - seconds_taken, seconds_expected), - end='', file=sys.stderr) - sys.stderr.flush() - - nick_stats = dict() - - for rev in xrange(1, latest_revision + 1): - if rev == 1 and args.show_progress: - indicate_progress(rev, before_work=True) - - if args.nick_stat_mode: - committer_name = client.revpropget( - 'svn:author', args.repo_uri, - pysvn.Revision(pysvn.opt_revision_kind.number, rev))[1] - if not committer_name: - committer_name = args.unknown_committer_name - (first_commit_rev, last_commit_rev, commit_count) \ - = nick_stats.get(committer_name, (None, None, 0)) - - if first_commit_rev is None: - first_commit_rev = rev - last_commit_rev = rev - commit_count = commit_count + 1 - - nick_stats[committer_name] = (first_commit_rev, last_commit_rev, - commit_count) - - if args.show_progress: - indicate_progress(rev) - continue - - summary = client.diff_summarize( - args.repo_uri, - revision1=pysvn.Revision(pysvn.opt_revision_kind.number, rev - 1), - url_or_path2=args.repo_uri, - revision2=pysvn.Revision(pysvn.opt_revision_kind.number, rev), - recurse=True, - ignore_ancestry=True) - - def is_directory_addition(summary_entry): - return (summary_entry.summarize_kind - == pysvn.diff_summarize_kind.added - and summary_entry.node_kind == pysvn.node_kind.dir) - - def is_directory_deletion(summary_entry): - return (summary_entry.summarize_kind - == pysvn.diff_summarize_kind.delete - and summary_entry.node_kind == pysvn.node_kind.dir) - - dirs_added = [e.path for e in summary if is_directory_addition(e)] - for d in dirs_added: - sub_tree = tree - for name in d.split('/'): - if name not in sub_tree: - added_on_rev, last_deleted_on_rev, children \ - = rev, None, dict() - sub_tree[name] = (added_on_rev, last_deleted_on_rev, - children) - else: - added_on_rev, last_deleted_on_rev, children \ - = sub_tree[name] - if last_deleted_on_rev is not None: - sub_tree[name] = (added_on_rev, None, children) - sub_tree = children - - def mark_deleted_recursively(sub_tree, name, rev): - added_on_rev, last_deleted_on_rev, children = sub_tree[name] - if last_deleted_on_rev is None: - sub_tree[name] = (added_on_rev, rev, children) - for child_name in children.keys(): - mark_deleted_recursively(children, child_name, rev) - - dirs_deleted = [e.path for e in summary if is_directory_deletion(e)] - for d in dirs_deleted: - sub_tree = tree - comps = d.split('/') - comps_len = len(comps) - for i, name in enumerate(comps): - if i == comps_len - 1: - mark_deleted_recursively(sub_tree, name, rev) - else: - added_on_rev, last_deleted_on_rev, children \ - = sub_tree[name] - sub_tree = children - - if args.show_progress: - indicate_progress(rev) - - if args.show_progress: - print(file=sys.stderr) - print(file=sys.stderr) - sys.stderr.flush() - - # NOTE: Leaves are files and empty directories - if args.nick_stat_mode: - dump_nick_stats(nick_stats, digit_count(latest_revision), config=args) - else: - dump_tree(tree, digit_count(latest_revision), latest_revision, - config=args) - - -if __name__ == '__main__': - main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/svneverever-1.4.2/modules/svneverever/version.py new/svneverever-1.7.1/modules/svneverever/version.py --- old/svneverever-1.4.2/modules/svneverever/version.py 2019-07-16 23:47:25.000000000 +0200 +++ new/svneverever-1.7.1/modules/svneverever/version.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,5 +0,0 @@ -# Copyright (C) 2010-2019 Sebastian Pipping <sebast...@pipping.org> -# Licensed under GPL v3 or later - -VERSION = (1, 4, 2) -VERSION_STR = '.'.join(str(e) for e in VERSION) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/svneverever-1.4.2/setup.py new/svneverever-1.7.1/setup.py --- old/svneverever-1.4.2/setup.py 2019-07-16 23:47:25.000000000 +0200 +++ new/svneverever-1.7.1/setup.py 2021-07-09 00:50:45.000000000 +0200 @@ -1,30 +1,36 @@ -#!/usr/bin/env python -# Copyright (C) 2010-2019 Sebastian Pipping <sebast...@pipping.org> +#!/usr/bin/env python3 +# Copyright (C) 2010-2021 Sebastian Pipping <sebast...@pipping.org> # Licensed under GPL v3 or later -from setuptools import setup +from setuptools import find_packages, setup import sys -sys.path.insert(0, 'modules') +sys.path.insert(0, '.') from svneverever.version import VERSION_STR # noqa: E402 setup( name='svneverever', description='Tool collecting path entries across SVN history', + long_description=open('README.md').read(), + long_description_content_type='text/markdown', license='GPL v3 or later', version=VERSION_STR, url='https://github.com/hartwork/svneverever', author='Sebastian Pipping', author_email='sebast...@pipping.org', - package_dir={'': 'modules', }, - packages=['svneverever', ], + packages=find_packages(), entry_points={ 'console_scripts': [ 'svneverever = svneverever.__main__:main', ], }, + python_requires='>=3.6', + setup_requires=[ + 'setuptools>=38.6.0', # for long_description_content_type + ], install_requires=[ - 'six', + # Package 'pysvn' on PyPI is not the PySVN off SourceForge + # that we need! ], classifiers=[ 'Development Status :: 5 - Production/Stable', @@ -35,11 +41,11 @@ ':: GNU General Public License v3 or later (GPLv3+)', 'Natural Language :: English', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3 :: Only', 'Topic :: Software Development :: Version Control', 'Topic :: Utilities', ], diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/svneverever-1.4.2/svneverever/__main__.py new/svneverever-1.7.1/svneverever/__main__.py --- old/svneverever-1.4.2/svneverever/__main__.py 1970-01-01 01:00:00.000000000 +0100 +++ new/svneverever-1.7.1/svneverever/__main__.py 2021-07-09 00:50:45.000000000 +0200 @@ -0,0 +1,441 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2021 Sebastian Pipping <sebast...@pipping.org> +# Copyright (C) 2011 Wouter Haffmans <wou...@boxplosive.nl> +# Copyright (C) 2019 Kevin Lane <kevin.lan...@outlook.com> +# Licensed under GPL v3 or later +# +from __future__ import print_function + +import argparse +import getpass +import math +import os +import re +import signal +import sys +import time +from collections import namedtuple +from textwrap import dedent +from urllib.parse import quote as urllib_parse_quote + +import pysvn + +from .version import VERSION_STR + +_EPILOG = """\ +Please report bugs at https://github.com/hartwork/svneverever. Thank you! +""" + + +_OsTerminalSize = namedtuple('_OsTerminalSize', ['columns', 'lines']) + + +class _SvnCheckoutDetected(ValueError): + def __init__(self, repo_location): + self._repo_location = repo_location + + def __str__(self): + return (f'Directory {self._repo_location!r}' + ' does not contain an SVN repository' + ' but an SVN checkout.') + + +def _get_terminal_size_or_default(): + try: + return int(os.environ['COLUMNS']) + except (KeyError, ValueError): + pass + + query_fd = os.get_terminal_size + for fd in (0, 1, 2): + try: + return query_fd(fd) + except Exception: + pass + + try: + fd = os.open(os.ctermid(), os.O_RDONLY) + try: + return query_fd(fd) + finally: + os.close(fd) + except Exception: + pass + + return _OsTerminalSize(columns=80, lines=24) + + +def _for_print(text): + if sys.version_info >= (3, ): + return text + + return text.encode(sys.stdout.encoding or 'UTF-8', 'replace') + + +def dump_tree(t, revision_digits, latest_revision, config, + level=0, branch_level=-3, tag_level=-3, parent_dir=''): + def indent_print(line_start, text): + if config.flat_tree: + level_text = parent_dir + else: + level_text = ' '*(4*level) + if config.show_numbers: + print('{} {}{}'.format(line_start, level_text, _for_print(text))) + else: + print('{}{}'.format(level_text, _for_print(text))) + + items = ((k, v) for k, v in t.items() if k) + + if ((branch_level + 2 == level) and not config.show_branches) \ + or ((tag_level + 2 == level) and not config.show_tags) \ + or level >= config.max_depth: + if items and config.show_dots: + line_start = ' '*(1 + revision_digits + 2 + revision_digits + 1) + if config.flat_tree: + indent_print(line_start, '/[..]') + else: + indent_print(line_start, '[..]') + return + + for k, (added_on_rev, last_deleted_on_rev, children) in sorted(items): + format = '(%%%dd; %%%dd)' % (revision_digits, revision_digits) + if last_deleted_on_rev is not None: + last_seen_rev = last_deleted_on_rev - 1 + else: + last_seen_rev = latest_revision + visual_rev = format % (added_on_rev, last_seen_rev) + + indent_print(visual_rev, '/%s' % k) + + bl = branch_level + tl = tag_level + if k == 'branches': + bl = level + elif k == 'tags': + tl = level + dump_tree(children, revision_digits, latest_revision, config, + level=level + 1, branch_level=bl, tag_level=tl, + parent_dir='{}/{}'.format(parent_dir, k)) + + +def dump_nick_stats(nick_stats, revision_digits, config): + if config.show_numbers: + format = "%%%dd (%%%dd; %%%dd) %%s" % (revision_digits, + revision_digits, + revision_digits) + for nick, (first_commit_rev, last_commit_rev, commit_count) \ + in sorted(nick_stats.items()): + print(format % (commit_count, first_commit_rev, last_commit_rev, + _for_print(nick))) + else: + for nick, (first_commit_rev, last_commit_rev, commit_count) \ + in sorted(nick_stats.items()): + print(_for_print(nick)) + + +def ensure_uri(text): + svn_uri_detector = re.compile('^[A-Za-z+]+://') + if svn_uri_detector.search(text): + return text + else: + abspath = os.path.abspath(text) + if os.path.exists(os.path.join(abspath, '.svn')): + raise _SvnCheckoutDetected(abspath) + return 'file://%s' % urllib_parse_quote(abspath) + + +def digit_count(n): + if n == 0: + return 1 + assert(n > 0) + return int(math.floor(math.log10(n)) + 1) + + +def hms(seconds): + seconds = math.ceil(seconds) + h = int(seconds / 3600) + seconds = seconds - h*3600 + m = int(seconds / 60) + seconds = seconds - m*60 + return h, m, seconds + + +def make_progress_bar(percent, width, seconds_taken, seconds_expected): + other_len = (6 + 1) + 2 + (1 + 8 + 1 + 9 + 1) + 3 + 1 + assert(width > 0) + bar_content_len = width - other_len + assert(bar_content_len >= 0) + fill_len = int(percent * bar_content_len / 100) + open_len = bar_content_len - fill_len + seconds_remaining = seconds_expected - seconds_taken + hr, mr, sr = hms(seconds_remaining) + if hr > 99: + hr = 99 + return ('%6.2f%% (%02d:%02d:%02d remaining) [%s%s]' + % (percent, hr, mr, sr, '#'*fill_len, ' '*open_len)) + + +def command_line(): + parser = argparse.ArgumentParser( + prog='svneverever', + description='Collects path entries across SVN history', + epilog=_EPILOG, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument( + '--version', + action='version', version='%(prog)s ' + VERSION_STR) + parser.add_argument( + 'repo_uri', + metavar='REPOSITORY', action='store', + help='Path or URI to SVN repository') + + modes = parser.add_argument_group('mode selection arguments') + modes.add_argument( + '--committers', + dest='nick_stat_mode', action='store_true', default=False, + help='Collect committer names instead of path information ' + '(default: disabled)') + + common = parser.add_argument_group('common arguments') + common.add_argument( + '--no-numbers', + dest='show_numbers', action='store_false', default=True, + help='Hide numbers, e.g. revision ranges (default: disabled)') + common.add_argument( + '--no-progress', + dest='show_progress', action='store_false', default=True, + help='Hide progress bar (default: disabled)') + common.add_argument( + '--non-interactive', + dest='interactive', action='store_false', default=True, + help='Will not ask for input (e.g. login credentials) if required' + ' (default: ask if required)') + + path_tree_mode = parser.add_argument_group('path tree mode arguments') + path_tree_mode.add_argument( + '--tags', + dest='show_tags', action='store_true', default=False, + help='Show content of tag folders (default: disabled)') + path_tree_mode.add_argument( + '--branches', + dest='show_branches', action='store_true', default=False, + help='Show content of branch folders (default: disabled)') + path_tree_mode.add_argument( + '--no-dots', + dest='show_dots', action='store_false', default=True, + help='Hide "[..]" omission marker (default: disabled)') + path_tree_mode.add_argument( + '--depth', + dest='max_depth', metavar='DEPTH', action='store', + type=int, default=-1, + help='Maximum depth to print (starting at 1)') + path_tree_mode.add_argument( + '--flatten', + dest='flat_tree', action='store_true', default=False, + help='Flatten tree (default: disabled)') + + committer_mode = parser.add_argument_group('committer mode arguments') + committer_mode.add_argument( + '--unknown-committer', + dest='unknown_committer_name', metavar='NAME', default='<unknown>', + help='Committer name to use for commits' + ' without a proper svn:author property (default: "%(default)s")') + + args = parser.parse_args() + + if args.max_depth < 1: + args.max_depth = sys.maxsize + + return args + + +def _login(realm, username, may_save, _tries): + if _tries > 0: + print('ERROR: Credentials not accepted by SVN, please try again.', + file=sys.stderr) + + try: + if username: + print('Username: {} (as requested by SVN)'.format(username), + file=sys.stderr) + else: + print('Username: ', end='', file=sys.stderr) + username = input('') + password = getpass.getpass('Password: ') + print(file=sys.stderr) + return True, username, password, False + except (KeyboardInterrupt, EOFError): + print(file=sys.stderr) + print('Operation cancelled.', file=sys.stderr) + sys.exit(128 + signal.SIGINT) + + +def _create_login_callback(): + tries = 0 + + def login_with_try_counter(*args, **kvargs): + nonlocal tries + + kvargs['_tries'] = tries + try: + return _login(*args, **kvargs) + finally: + tries += 1 + + return login_with_try_counter + + +def _check_for_suitable_pysvn(): + if not hasattr(pysvn, 'ClientError'): + print(dedent("""\ + ERROR: You need PySVN 1.x.x off SourceForge (https://pysvn.sourceforge.io/). + Package "pysvn" on PyPI is something else, unfortunately. + """), file=sys.stderr) # noqa: E501 + sys.exit(2) + + +def main(): + args = command_line() + + _check_for_suitable_pysvn() + + try: + args.repo_uri = ensure_uri(args.repo_uri) + except _SvnCheckoutDetected as e: + print('ERROR: %s' % str(e), file=sys.stderr) + sys.exit(3) + + # Build tree from repo + client = pysvn.Client() + if args.interactive: + client.callback_get_login = _create_login_callback() + tree = dict() + try: + latest_revision = client.info2( + args.repo_uri, recurse=False)[0][1]['last_changed_rev'].number + except (pysvn.ClientError) as e: + if str(e) == 'callback_get_login required': + print('ERROR: SVN Repository requires login credentials' + '. Please run without --non-interactive switch.', + file=sys.stderr) + else: + print('ERROR: %s' % str(e), file=sys.stderr) + sys.exit(1) + + start_time = time.time() + print('Analyzing %d revisions...' % latest_revision, file=sys.stderr) + width = _get_terminal_size_or_default().columns + + def indicate_progress(rev, before_work=False): + percent = rev * 100.0 / latest_revision + seconds_taken = time.time() - start_time + seconds_expected = seconds_taken / float(rev) * latest_revision + if (rev == latest_revision) and not before_work: + percent = 100 + seconds_expected = seconds_taken + print('\r' + make_progress_bar(percent, width, + seconds_taken, seconds_expected), + end='', file=sys.stderr) + sys.stderr.flush() + + nick_stats = dict() + + for rev in range(1, latest_revision + 1): + if rev == 1 and args.show_progress: + indicate_progress(rev, before_work=True) + + if args.nick_stat_mode: + committer_name = client.revpropget( + 'svn:author', args.repo_uri, + pysvn.Revision(pysvn.opt_revision_kind.number, rev))[1] + if not committer_name: + committer_name = args.unknown_committer_name + (first_commit_rev, last_commit_rev, commit_count) \ + = nick_stats.get(committer_name, (None, None, 0)) + + if first_commit_rev is None: + first_commit_rev = rev + last_commit_rev = rev + commit_count = commit_count + 1 + + nick_stats[committer_name] = (first_commit_rev, last_commit_rev, + commit_count) + + if args.show_progress: + indicate_progress(rev) + continue + + summary = client.diff_summarize( + args.repo_uri, + revision1=pysvn.Revision(pysvn.opt_revision_kind.number, rev - 1), + url_or_path2=args.repo_uri, + revision2=pysvn.Revision(pysvn.opt_revision_kind.number, rev), + recurse=True, + ignore_ancestry=True) + + def is_directory_addition(summary_entry): + return (summary_entry.summarize_kind + == pysvn.diff_summarize_kind.added + and summary_entry.node_kind == pysvn.node_kind.dir) + + def is_directory_deletion(summary_entry): + return (summary_entry.summarize_kind + == pysvn.diff_summarize_kind.delete + and summary_entry.node_kind == pysvn.node_kind.dir) + + dirs_added = [e.path for e in summary if is_directory_addition(e)] + for d in dirs_added: + sub_tree = tree + for name in d.split('/'): + if name not in sub_tree: + added_on_rev, last_deleted_on_rev, children \ + = rev, None, dict() + sub_tree[name] = (added_on_rev, last_deleted_on_rev, + children) + else: + added_on_rev, last_deleted_on_rev, children \ + = sub_tree[name] + if last_deleted_on_rev is not None: + sub_tree[name] = (added_on_rev, None, children) + sub_tree = children + + def mark_deleted_recursively(sub_tree, name, rev): + added_on_rev, last_deleted_on_rev, children = sub_tree[name] + if last_deleted_on_rev is None: + sub_tree[name] = (added_on_rev, rev, children) + for child_name in children.keys(): + mark_deleted_recursively(children, child_name, rev) + + dirs_deleted = [e.path for e in summary if is_directory_deletion(e)] + for d in dirs_deleted: + sub_tree = tree + comps = d.split('/') + comps_len = len(comps) + for i, name in enumerate(comps): + if i == comps_len - 1: + mark_deleted_recursively(sub_tree, name, rev) + else: + added_on_rev, last_deleted_on_rev, children \ + = sub_tree[name] + sub_tree = children + + if args.show_progress: + indicate_progress(rev) + + if args.show_progress: + print(file=sys.stderr) + print(file=sys.stderr) + sys.stderr.flush() + + # NOTE: Leaves are files and empty directories + if args.nick_stat_mode: + dump_nick_stats(nick_stats, digit_count(latest_revision), config=args) + else: + dump_tree(tree, digit_count(latest_revision), latest_revision, + config=args) + + +if __name__ == '__main__': + main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/svneverever-1.4.2/svneverever/version.py new/svneverever-1.7.1/svneverever/version.py --- old/svneverever-1.4.2/svneverever/version.py 1970-01-01 01:00:00.000000000 +0100 +++ new/svneverever-1.7.1/svneverever/version.py 2021-07-09 00:50:45.000000000 +0200 @@ -0,0 +1,5 @@ +# Copyright (C) 2010-2021 Sebastian Pipping <sebast...@pipping.org> +# Licensed under GPL v3 or later + +VERSION = (1, 7, 1) +VERSION_STR = '.'.join(str(e) for e in VERSION)