Compare commits

...

120 Commits

Author SHA1 Message Date
Adam Waldenberg b8375442a7 Add missing changes to CHANGES.txt 2020-10-19 11:24:33 +02:00
Adam Waldenberg 6d77989e34 Fix logic error in the threading code of the changes module (Fixes #118).
This particular problem is also briefly mentioned in issue #115. The
fix resolves the overflow problem resulting in an array with a dangling
"None" type at the end. It also takes care of a race condition where
two threads by accident could update the same data.
2017-05-15 03:06:53 +02:00
Adam Waldenberg c80c822892 Ignore any unicode_escape decode fails when decoding emails (Fixes #122).
This is only needed with emails that have escape characters, as described
in issue #46. If the call fails, it will most likely not affect us and
can be safely ignored.
2017-05-13 16:41:54 +02:00
Adam Waldenberg b3fbb3e3c3 Removed dangling semicolon in changes module. 2017-05-13 16:36:05 +02:00
Bart van Andel 40cfd0ac84 Bump version for npm 2017-05-13 16:14:45 +02:00
Bart van Andel 1e5f5959db Convert line endings of Python files to Unix-style before publishing 2017-05-13 16:14:45 +02:00
Bart van Andel a8e0de7910 Shorten description so it doesn't get truncated online 2017-05-13 16:14:45 +02:00
Bart van Andel 09c5e50381 Add some more keywords 2017-05-13 16:14:45 +02:00
Bart van Andel 55434ff76a Prefer npm global install 2017-05-13 16:14:45 +02:00
Bart van Andel 3d11cdce44 Setup npm package 2017-05-13 16:14:45 +02:00
GuillermoMI 315f4079ac Fix for PHP close comment token. 2017-05-13 16:06:38 +02:00
Adam Waldenberg d0798d8358 Set ref to HEAD when starting changes analysis i (Fixes #132).
This gets rid of any invalid refs being set at a subsequent point during
execution.
2017-05-13 15:43:09 +02:00
Adam Waldenberg a56680c4b4 Handle git-rev-list and git-ls-tree with empty changesets (#132, #115).
If the changeset was empty or filtered with no matching files a bunch of
errors would be thrown from git with no proper results being returned
back to gitinspector.

We now pipe stderr (catching the output) and also check the return code
when running these commands.
2017-05-13 15:41:13 +02:00
Adam Waldenberg 8cff4bd208 Removed logically redundant part of an if statement.
The len(self.commits) > 0 check was already being performed one level up.
2017-05-13 15:41:13 +02:00
Adam Waldenberg 983d3d05bd When generating the report, summed changes need to be non-empty (#132).
Previoulsy, we were just checking against the changes returned from the
last repository analyzed - which of course broke the whole thing if that
repository had no matching commits!
2017-05-13 15:39:38 +02:00
Adam Waldenberg 9f336e63ce Faulty clone code made it possible to run gitinspector in sub-directories.
This resulted in undefined behavior. The base directory was being
incorrectly defined when cloning did not occur.
2017-05-12 15:31:28 +02:00
Adam Waldenberg ba1c3341fe The typo "defaut" should be "default" in documentation (Fixes #113). 2016-07-19 04:04:31 +02:00
Adam Waldenberg e543eeaf58 Only include valid extensions in blame output (#95). 2016-02-03 14:22:19 +01:00
Adam Waldenberg 6e0365e8ba Fix inconsistent behavior with -f "**" (#95).
Improved by taking advantage of the detected file types during the first
pass in the changes module.
2016-02-03 11:47:00 +01:00
Adam Waldenberg 139f5306f4 Better support terminals behaving like urxvt (Fixes #101).
This terminal was allowing backspaces (\b) to bypass column zero on the
current row, completely breaking the current way gitinspector was
clearing text outputted to the currently active row.
2016-01-21 00:22:52 +01:00
Adam Waldenberg 9b5bbc469f Removed superfluous quotation character in the JSON metrics output. 2015-12-18 08:41:46 +01:00
Adam Waldenberg a9d519c93b Recognize Rust programming language (.rs) comments (#83). 2015-12-17 03:15:22 +01:00
Adam Waldenberg 9aa4aba336 Removed size parameter from gravatar URL's in JSON output (#50). 2015-12-17 03:08:33 +01:00
Adam Waldenberg 6ef9936508 Fix some minor typos in the documentation.
Also regenerated the documentation using AsciiDoc.
2015-12-14 05:19:09 +01:00
Adam Waldenberg 150e316918 Add documentation about JSON output to the AsciiDoc file. 2015-12-14 05:17:11 +01:00
Adam Waldenberg ba049a0367 Fix AttributeError thrown when no extensions were detected (Fixes #91).
The bug was introduced with the commit ecc67a3 and was caused by the
initialization of an empty Changes object.

This fix makes sure that the __iadd__() method properly handles empty
change sets and returns a satisfactory object back when completing
execution.
2015-12-12 07:49:58 +01:00
Gregrs 3c48789890 Recognise Lilypond (.ly and .ily) comments 2015-12-10 07:34:18 +00:00
Adam Waldenberg 533bba64c6 Recognize comments in Robot Framework (.robot) files (#88). 2015-12-09 09:30:49 +01:00
Adam Waldenberg 4ee02f5907 Recognize Go language (.go) comments (#86). 2015-11-27 23:41:53 +01:00
Adam Waldenberg 5275521a9a Added author and license meta data to gitinspector piclet image. 2015-11-26 00:58:30 +01:00
Adam Waldenberg ecc67a31a5 Repositories are now merged with __iadd__() instead of __add__().
Thus, we are overriding the "+=" operator instead of "+". This
implementation results in a much cleaner solution as we were directly
updating "self" when overriding __add__(), something which isn't really
an acceptable solution as we were changing the supplied in-parameters.
2015-11-25 17:28:50 +01:00
Adam Waldenberg 9bd4b979b3 Fixed confusing variable names in JSON output functions and methods.
These functions and methods were originally based on the XML output.
Consequently, those variable names were also preserved. All "<name>_xml"
variables are now named "<name>_json" instead.
2015-11-24 21:20:35 +01:00
Adam Waldenberg bfde70db91 Include author email in JSON and HTML outputs (Fixes #85). 2015-11-24 21:02:32 +01:00
Adam Waldenberg 12f8c8a192 Use repos_string instead of repos[0].name in JSON output in format.py. 2015-11-07 05:55:14 +01:00
Adam Waldenberg 9a22763e64 Added missing line break in format.__get_zip_file_content__(...). 2015-11-07 05:52:23 +01:00
Adam Waldenberg 346645d655 The format.__output_html_template__(...) function now cleans up. 2015-11-07 05:51:40 +01:00
Adam Waldenberg 91d94446a7 The logo in the HTML output now links to the gitinspector page. 2015-11-04 05:56:52 +01:00
Adam Waldenberg 88d840dd51 Gitinspector now has a better and more clear logo. 2015-11-04 05:31:24 +01:00
Adam Waldenberg 26f77e0ee4 The XML output now prints repositories in a proper XML list.
If one repository is found, the following format is used:

<repository>name</repository>

If multiple repositories are found, the following format is used:

<repositories>
	<repository>name</repository>
	<repository>name</repository>
	...
</repositories>
2015-11-03 23:00:17 +01:00
Adam Waldenberg 4d6ecd3123 Avoid hyphen characters ("-") in JSON properties.
Using that character is obviously a bad idea, as it is reserved
for substraction in most programming languages; making output a
hassle to parse.
2015-11-03 22:52:17 +01:00
Adam Waldenberg 9b3a5b674e Some output was missing in the JSON output.
Repository names, gitinspector version and report date were all missing
from the output when JSON was selected.
2015-11-03 22:42:23 +01:00
Adam Waldenberg 87fd5b467f Upgraded the JQuery version in the flot archive to 1.9.1 (#28).
This is the minimal version required by Bootstrap3 (which we will
base the new responsive HTML theme on). This upgrade required changes
to the JavaScript code, as toggle(...) has been deprecated since
version 1.9.x of JQuery.
2015-11-03 04:25:40 +01:00
Adam Waldenberg 4fd918fca4 Fixed some pylint violations. 2015-11-03 00:44:47 +01:00
Adam Waldenberg 949a301698 Fixed a few typo mistakes in the HTML output of the format module. 2015-11-03 00:36:05 +01:00
Adam Waldenberg 211060c20e Updated the flot archive to version 0.8.3. 2015-11-03 00:33:16 +01:00
Adam Waldenberg 9ada057d81 Progress output now includes repository name (#24).
The name of the repository is only printed when multiple repositories
are specified. Otherwise, gitinspector falls back to the previous
behavior
2015-11-02 18:22:56 +01:00
Adam Waldenberg 7dda8d34b5 Made terminal.output_progress(...) aware of the terminal width.
Instead of clearing the row using the "\b" character on each
iteration, the function now passes a carriage return ("\r") instead.
Furthermore, the row is now always completely cleared before
printing the progress.
2015-11-02 18:02:54 +01:00
Adam Waldenberg d5106a7302 Implemented validation and printing of all supplied repositories (#24).
Upon start-up, now directly passes all supplied paths to git in order
to check if all of them point to a valid git repository.

The clone.create() function now returns a list of Repository instances,
containing the name and location of the repository. This is later
used in the gitinspector module to execute and parse the repository.

format.output_header(...) now takes a list of Repository instances.
Thanks to this, each output format can print every supplied repository.
2015-11-02 02:30:28 +01:00
Adam Waldenberg 01bdbfaba1 basedir.get_basedir_git() now takes an optional path argument.
This enables us to change the working directory for the git commands
in the function upon request. This change is in preparation of the
completion of #24.
2015-11-01 17:19:46 +01:00
Adam Waldenberg 7f5b50cd0d Fixed formatting bug in the JSON output of the metrics module.
Sections in the metrics output were not being separated with a comma,
leading to invalid JSON.
2015-11-01 03:45:44 +01:00
Adam Waldenberg 802f18e7e5 The metrics module now supports multiple repositories (See issue #24). 2015-11-01 03:36:40 +01:00
Adam Waldenberg 7acf871ab1 Help text now mentions support for multiple repos (See issue #24). 2015-10-31 05:11:10 +01:00
Adam Waldenberg 98615ccbfc Support for multiple repositories is near completion (See issue #24).
Only the metrics module is lacking support. While the rest should work,
please note that it is completely untested as of now and probably
has some rough edges.
2015-10-31 05:10:00 +01:00
Adam Waldenberg 46b21db196 The changes module now supports multiple repositories (See issue #24).
Just as before, all other parts of gitinspector currently do not
support fetching statistics from multiple repositories and just
simply fetch data from the last repository specified.
2015-10-31 03:44:00 +01:00
Adam Waldenberg 3bae1be0cb Fixed faulty import in metricsoutput module. 2015-10-31 02:22:25 +01:00
Adam Waldenberg 9287b187f7 Fixed some pylint violations. This also takes care of some bugs. 2015-10-31 00:03:51 +01:00
Adam Waldenberg dd2feedbe4 Fixed invalid indentation at begining of file in gitinspector.py. 2015-10-30 23:54:04 +01:00
Adam Waldenberg e0941fdcf1 The gitinspector.py entry module now considers all repositories.
While they are considered, they are not yet being merged. Statistics
are only calculated for the last repository passed.

This is being done in preparation of completing issue #24.
2015-10-30 23:46:13 +01:00
Adam Waldenberg 109a94e1e7 The basedir module is no longer persistent (doesn't remember state).
This is required in order for it to be callable multiple times for
multiple repositories (Needed for #24).
2015-10-30 23:29:03 +01:00
Adam Waldenberg 258eefa1e7 The clone module can now handle multiple repositories.
This is needed to allow gitinspector to be able to clone multiple
repositories during the same execution (Needed for #24).
2015-10-30 23:16:33 +01:00
Adam Waldenberg d88ff2c5b9 Slight cleanup in gitinspector.py.
Renamed all "local" declarations in main().
2015-10-30 03:41:09 +01:00
Adam Waldenberg c01a59430c Rewrote the config module into a class (GitConfig).
This class takes the initialization variable "global_only" which
will be set in the future whenever multiple repositories are
specified (See issue #24).

This will make sure that the git configuration is only read from the
global settings instead of per-repository.
2015-10-30 01:24:33 +01:00
Adam Waldenberg 5af89f798a Added a note about the supplied Debian packages to README.md. 2015-10-29 03:58:09 +01:00
Adam Waldenberg d30715cc84 The changes module now also outputs the progress when in text mode.
Furthermore, the output also shows the pass currently running. This is
important, as the progress goes from 0 to 100%, twice.
2015-10-29 03:37:08 +01:00
Adam Waldenberg ce91c4176a Moved blame.Blame.output_progress to terminal.output_progress.
This enables us to output progress from multiple modules.
2015-10-29 03:36:53 +01:00
Adam Waldenberg 6606d8b13c Progress from the blame module is now printed regardless of --hard. 2015-10-29 03:36:49 +01:00
Adam Waldenberg 1ed9b5e3cf Re-factored the changes module and made it more independent.
This will make it easier to add support for the merging of statistics from
multiple repositories (as discussed in issue #24).
2015-10-29 03:09:46 +01:00
Adam Waldenberg fa04eb57f3 Bumped the version number to the next development iteration. 2015-10-29 02:25:20 +01:00
Adam Waldenberg d941487280 Imported CHANGES.txt from most recent extra-release branch. 2015-10-29 01:32:15 +01:00
Adam Waldenberg d8dfecb19a Updated typo in RESPONSIBILITIES_INFO_TEXT in all translations.
Also regenerated all .mo files, so these should now all be up-to-date.
2015-10-29 00:11:26 +01:00
Adam Waldenberg 4bd723eea0 Chinese help text would not fit on a 80 character terminal. 2015-10-28 23:59:57 +01:00
Adam Waldenberg 859224f36f Wrong translator specified in Chinese translation. 2015-10-28 23:56:48 +01:00
Bill Wang 5ee5127b15 Fixed typos in RESPONSIBILITIES_INFO_TEXT.
This also requires changes to the templates of the translations.
2015-10-28 23:23:33 +01:00
Bill Wang 75b528a84a Updated Chinese translation (Fixes #11). 2015-10-28 23:23:23 +01:00
Adam Waldenberg 8a0ba3dca1 Updated the header and .mo file of the Italian translation. 2015-10-28 01:01:39 +01:00
Luca Motta 58a983ed27 Updated the Italian translation (Fixes #20). 2015-10-28 00:49:39 +01:00
Adam Waldenberg eec6741cfb Updated header information of french translation and generated .mo file. 2015-10-26 02:19:42 +01:00
Yannick Moy 21c6346868 Updated French translation: help messages. 2015-10-25 21:56:13 +01:00
Adam Waldenberg 78831c1b9b Fixed slight mistake and updated header information in Polish translation.
Also took the opportunity to regenerate the .mo file.
2015-10-24 23:24:28 +02:00
Kamila Chyla 79e390afe8 Updated Polish translation: help messages. 2015-10-24 19:00:54 +02:00
Adam Waldenberg 81e18ff075 Small tweaks to the header of the german translation.
It now correctly references the current pot and version. Consequently, it
should be usable without generating a warning from the current
development iteration of gitinspector.
2015-10-24 17:34:21 +02:00
xxyy b0e7bb5ae9 Update German translation; Fixes #25
This commit updates the German translation. Changes in translation
include updating for changed messages in upstream and some rewording.
Also, this commit includes a different translation for "cyclomatic
complexity" because the previously used one (by inventor's name)
might be less clear than the current, literal translation, which seems
more intuitive, especially in a world where English is dominant in
software dev, so people are more likely to know only the English word.

Some translations have been changed to be more clear and to the point.
Some comments have been added to messages that have been causing issues.
2015-10-24 12:20:49 +02:00
Adam Waldenberg b4eb5484ac README.md now includes a note about JSON being supported. 2015-10-24 03:08:35 +02:00
Adam Waldenberg bbe07f061d Added support for JSON output format (Fixes #50). 2015-10-24 02:44:45 +02:00
Adam Waldenberg cff8dd109b Fixed minor mistake in the filtering XML output.
Nothing that really affects functionality. However, a string was doing
some unnecessary string.format calls.
2015-10-22 00:36:11 +02:00
Adam Waldenberg 124636cb85 Restructured imports slightly. 2015-10-21 05:26:20 +02:00
Adam Waldenberg e9eab37c83 Added a new gitinspector.py entry script. 2015-10-21 05:07:47 +02:00
Marc Harper 59d9e5c7ae Try block unneeded in localization.py import 2015-10-20 09:51:42 -07:00
Marc Harper 4f3e0d3073 Restore relative imports of changes and filtering 2015-10-20 09:48:32 -07:00
Marc Harper 9abb9b3d56 Relative imports to fix packaging issues 2015-10-20 09:40:08 -07:00
Adam Waldenberg f2a4cb92d3 Always append the root gitinspector package to sys.path (Fixes #73).
With this change, gitinspector once again functions in conjunction with
egg installations. Also removed all forceful absolute imports, as these
really aren't needed.
2015-10-15 17:01:57 +02:00
Adam Waldenberg d333f7bdd2 Updated copyright notices of recently updated files. 2015-10-12 03:18:57 +02:00
Adam Waldenberg 1f2f120389 Fixed pylint violations reported by a newer version of pylint. 2015-10-12 03:15:30 +02:00
Adam Waldenberg 34337dec17 Refactored all outputable modules.
This prepares the source code for the changes discussed in issue #24.

Note that this is just a quick restructuring in order to see the resulting
classes and separation. More work will be done to make it more elegant
and with less dependencies between modules.
2015-10-12 03:03:07 +02:00
Adam Waldenberg 6aa41ade9f Bumped the version number to the next development iteration. 2015-10-12 00:48:30 +02:00
Adam Waldenberg fa483b4327 Bumped the version number to 0.4.2. 2015-10-12 00:47:46 +02:00
Adam Waldenberg 5259b76b94 The localization module now warns when finding an out of date translation.
While allowing us to include translations that are not quite up to date
with the current development branch in releases, it also serves to inform
the user that they are using a translation that is broken or incomplete.
2015-10-09 03:02:06 +02:00
Adam Waldenberg 4b92e7a3cc Updated the version string of current up to date translations.
These version strings now reference the current development iteration and
will be updated each time we bump revision.
2015-10-09 02:57:48 +02:00
Agustín Cañas 9368898d6d Updated Spanish translation. Ready for next release 2015-10-08 23:49:31 +02:00
Adam Waldenberg 38df413ebf Global cleanup fixing some pylint violations. 2015-10-05 06:17:10 +02:00
Adam Waldenberg c0cb2d2801 Running setup.py under Python 3 gave an import exception. 2015-10-04 16:05:06 +02:00
Adam Waldenberg 07f17f3d2e Updated Swedish translation. 2015-10-02 04:13:58 +02:00
Adam Waldenberg 47b11addd9 Added an updated messages.pot.
The only thing that has changed since the last .pot file is the help text
(--help). New text has been added to the documentation for the "-x" flag
and the "-f" flag.
2015-10-02 03:51:13 +02:00
Adam Waldenberg aa727a95c3 Documentation update to include the new features on master. 2015-10-01 04:37:20 +02:00
Adam Waldenberg 3e88fcb71a Added the possibility to include all file extensions in the analysis.
This functionality was briefly discussed in issue #61. To make
gitinspector consider all file extensions, it is now possible to supply
a  double asterisk "**" to the list of file extensions.
2015-10-01 03:59:44 +02:00
Adam Waldenberg 243e52b5de The changes module could crash upon no detected file types (Fixes #68).
The sub list flattening happening after all the "changes threads" have
run should be happening before the len(self.commits) check, not after.
2015-09-30 15:10:46 +02:00
Adam Waldenberg e4827ee58e The changes module crashed on empty commit/revision lists (Fixes #68).
Once again introduced with aeb9ad6, this could happen whenever a
repository had no commits or if the history was being filtered with
--since and/or --until and no commits were being detected within that
interval.
2015-09-29 22:09:05 +02:00
Adam Waldenberg 6d89cdf8c8 Added the maintainer of the Spanish translation to README.md. 2015-09-27 12:57:53 +02:00
Agustín Cañas ad36849afb Added Spanish translation (Closes #71). 2015-09-27 12:50:12 +02:00
Adam Waldenberg e3f741b518 Added support for extensionless files (Fixes #60).
This can be enabled by passing a "*" to the list of defined file
types (-f). Originally, the plan was to also implement support for
regular expressions. However, this was skipped, as it complicated how
expressions for the file types flag were being passed on the command line.
2015-09-26 01:52:04 +02:00
Adam Waldenberg 583b5fa753 Python 3 compatibility was broken with commit aeb9ad6 (Fixes #70). 2015-09-25 23:52:53 +02:00
Adam Waldenberg 582ffe7246 Fixed bug introduced with commit aeb9ad6 (Fixes #68).
The interval check was referencing commits instead of self.commits.
2015-09-24 21:40:57 +02:00
Adam Waldenberg a6c05cc619 Added support for comments and metrics in C# code (Fixes #59). 2015-09-24 04:11:38 +02:00
Adam Waldenberg da1553b57e Implemented filtering by commit message (Fixes #57).
When filtering, "git show" is called in order to pull the commit message
from the git log. While slow, it makes certain that the message itself is
pulled correctly, including any escape characters that might be present.
2015-09-24 03:38:35 +02:00
Adam Waldenberg f368c0019a Fixed mistake in the filtering module related to hash filtering.
FILTERING_EMAIL_INFO_TEXT was erroneously being defined twice.
2015-09-22 02:41:16 +02:00
Adam Waldenberg 5a18732112 Added missing file close() in blame module. 2015-09-20 01:43:40 +02:00
Adam Waldenberg b4b48deebd Printed extensions (-l) are now alphabetically sorted. 2015-09-20 01:42:54 +02:00
Adam Waldenberg aeb9ad69f9 Implemented threading in the changes module (Fixes #15).
This change results in a substantial speed up.
2015-09-19 04:32:02 +02:00
Adam Waldenberg 7a9eb69ab0 README.md links to cdn.rawgit.com changed to githubproxy.ejwa.se.
Developed using JavaEE, this is a (currently) private service with
caching for fetching files from GitHub projects. Unlike cdn.rawgit.com,
we don't cache files indefinitely. Instead, we use ETags to control
caching.
2015-09-11 03:51:53 +02:00
Adam Waldenberg f37bdb7c58 Bumped the version number to the next development iteration. 2015-09-08 02:03:11 +02:00
58 changed files with 2621 additions and 1438 deletions

2
.gitignore vendored
View File

@ -2,5 +2,7 @@ build
debian
deb_dist
dist
node_modules
*.egg-info
*.pyc
*.tgz

View File

@ -3,7 +3,7 @@ include-ids=yes
comment=yes
[MESSAGES CONTROL]
disable=C0111,R0801,W0232,W0603,W0622,W0702
disable=C0111,R0801,W0232,W0603,W0622,W0702,W0141
[DESIGN]

View File

@ -1,3 +1,29 @@
gitinspector (0.4.4)
Minor release with some minor fixes.
* Better support for additional terminals.
* -f "**" now properly ignores binary files.
gitinspector (0.4.3)
Minor release with updated language locales.
gitinspector (0.4.2)
Minor release with many small, but great improvements.
* The changes module is now threaded and uses all available cores. This
results in a significant speed up.
* Printed extensions are now alphabetically sorted.
* Ability to exclude commits with certain comments from statistics.
* Support for C# code and comments.
* Extensionless files can now be included in statistics.
* Ability to include all file extensions by specifying "**".
* Spanish translation.
gitinspector (0.4.1)
Maintenance release fixing several issues found in the previous version.

View File

@ -1,12 +1,12 @@
[![Latest release](https://img.shields.io/github/release/ejwa/gitinspector.svg?style=flat-square)](https://github.com/ejwa/gitinspector/releases/latest)
[![License](https://img.shields.io/github/license/ejwa/gitinspector.svg?style=flat-square)](https://github.com/ejwa/gitinspector/blob/master/LICENSE.txt)
<h2>
<img align="left" height="30px"
<img align="left" height="65px"
src="https://raw.githubusercontent.com/ejwa/gitinspector/master/gitinspector/html/gitinspector_piclet.png"/>
&nbsp;About Gitinspector
</h2>
<img align="right" width="30%" src="https://raw.github.com/wiki/ejwa/gitinspector/images/html_example.jpg" />
Gitinspector is a statistical analysis tool for git repositories. The defaut analysis shows general statistics per author, which can be complemented with a timeline analysis that shows the workload and activity of each author. Under normal operation, it filters the results to only show statistics about a number of given extensions and by default only includes source files in the statistical analysis.
Gitinspector is a statistical analysis tool for git repositories. The default analysis shows general statistics per author, which can be complemented with a timeline analysis that shows the workload and activity of each author. Under normal operation, it filters the results to only show statistics about a number of given extensions and by default only includes source files in the statistical analysis.
This tool was originally written to help fetch repository statistics from student projects in the course Object-oriented Programming Project (TDA367/DIT211) at Chalmers University of Technology and Gothenburg University.
@ -20,7 +20,7 @@ A full [Documentation](https://github.com/ejwa/gitinspector/wiki/Documentation)
* Can display a statistical timeline analysis.
* Scans for all filetypes (by extension) found in the repository.
* Multi-threaded; uses multiple instances of git to speed up analysis when possible.
* Supports HTML, XML and plain text output (console).
* Supports HTML, JSON, XML and plain text output (console).
* Can report violations of different code metrics.
### Example outputs
@ -28,12 +28,14 @@ Below are some example outputs for a number of famous open source projects. All
| Project name | | | | |
|---|---|---|---|---|
| Django | [HTML](http://cdn.rawgit.com/wiki/ejwa/gitinspector/examples/django_output.html) | [HTML Embedded](http://cdn.rawgit.com/wiki/ejwa/gitinspector/examples/django_output.emb.html) | [Plain Text](https://raw.github.com/wiki/ejwa/gitinspector/examples/django_output.txt) | [XML](https://raw.github.com/wiki/ejwa/gitinspector/examples/django_output.xml) |
| JQuery | [HTML](http://cdn.rawgit.com/wiki/ejwa/gitinspector/examples/jquery_output.html) | [HTML Embedded](http://cdn.rawgit.com/wiki/ejwa/gitinspector/examples/jquery_output.emb.html) | [Plain Text](https://raw.github.com/wiki/ejwa/gitinspector/examples/jquery_output.txt) | [XML](https://raw.github.com/wiki/ejwa/gitinspector/examples/jquery_output.xml) |
| Pango | [HTML](http://cdn.rawgit.com/wiki/ejwa/gitinspector/examples/pango_output.html) | [HTML Embedded](http://cdn.rawgit.com/wiki/ejwa/gitinspector/examples/pango_output.emb.html) | [Plain Text](https://raw.github.com/wiki/ejwa/gitinspector/examples/pango_output.txt) | [XML](https://raw.github.com/wiki/ejwa/gitinspector/examples/pango_output.xml) |
| Django | [HTML](http://githubproxy.ejwa.se/wiki/ejwa/gitinspector/examples/django_output.html) | [HTML Embedded](http://githubproxy.ejwa.se/wiki/ejwa/gitinspector/examples/django_output.emb.html) | [Plain Text](http://githubproxy.ejwa.se/wiki/ejwa/gitinspector/examples/django_output.txt) | [XML](http://githubproxy.ejwa.se/wiki/ejwa/gitinspector/examples/django_output.xml) |
| JQuery | [HTML](http://githubproxy.ejwa.se/wiki/ejwa/gitinspector/examples/jquery_output.html) | [HTML Embedded](http://githubproxy.ejwa.se/wiki/ejwa/gitinspector/examples/jquery_output.emb.html) | [Plain Text](http://githubproxy.ejwa.se/wiki/ejwa/gitinspector/examples/jquery_output.txt) | [XML](http://githubproxy.ejwa.se/wiki/ejwa/gitinspector/examples/jquery_output.xml) |
| Pango | [HTML](http://githubproxy.ejwa.se/wiki/ejwa/gitinspector/examples/pango_output.html) | [HTML Embedded](http://githubproxy.ejwa.se/wiki/ejwa/gitinspector/examples/pango_output.emb.html) | [Plain Text](http://githubproxy.ejwa.se/wiki/ejwa/gitinspector/examples/pango_output.txt) | [XML](http://githubproxy.ejwa.se/wiki/ejwa/gitinspector/examples/pango_output.xml) |
### The Team
* Adam Waldenberg, Lead maintainer and Swedish translation
* Agustín Cañas, Spanish translation
* Bart van Andel, npm package maintainer
* Bill Wang, Chinese translation
* Christian Kastner, Debian package maintainer
* Jiwon Kim, Korean translation
@ -45,5 +47,10 @@ Below are some example outputs for a number of famous open source projects. All
*We need translations for gitinspector!* If you are a gitinspector user, feel willing to help and have good language skills in any unsupported language we urge you to contact us. We also happily accept code patches. Please refer to [Contributing](https://github.com/ejwa/gitinspector/wiki/Contributing) for more information on how to contribute to the project.
### Packages
The Debian packages offered with releases of gitinspector are unofficial and very simple packages generated with [stdeb](https://github.com/astraw/stdeb). Christian Kastner is maintaining the official Debian packages. You can check the current status on the [Debian Package Tracker](https://tracker.debian.org/pkg/gitinspector). Consequently, there are official packages for many Debian based distributions installable via *apt-get*.
An [npm](https://npmjs.com) package is provided for convenience as well. To install it globally, execute `npm i -g gitinspector`.
### License
gitinspector is licensed under the *GNU GPL v3*. The gitinspector logo is partly based on the git logo; based on the work of Jason Long. The logo is licensed under the *Creative Commons Attribution 3.0 Unported License*.

View File

@ -7,8 +7,8 @@ certain conditions; see the accompanying LICENSE.txt file for further details.
For questions regarding gitinspector you can contact the current maintainer
in charge at gitinspector@ejwa.se.
To run gitinspector; please start it via the gitinspector/gitinspector.py
script. Use the -h or --help flags to get help about available options.
To run gitinspector; please start it via the gitinspector.py script. Use
the -h or --help flags to get help about available options.
It is also possible to set gitinspector options using the "git config"
command. Refer to the project page at https://github.com/ejwa/gitinspector

View File

@ -2,12 +2,12 @@
.\" Title: gitinspector
.\" Author: [see the "AUTHOR" section]
.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
.\" Date: 08/24/2015
.\" Date: 12/14/2015
.\" Manual: The gitinspector Manual
.\" Source: gitinspector 0.4.0
.\" Source: gitinspector 0.4.2
.\" Language: English
.\"
.TH "GITINSPECTOR" "1" "08/24/2015" "gitinspector 0\&.4\&.0" "The gitinspector Manual"
.TH "GITINSPECTOR" "1" "12/14/2015" "gitinspector 0\&.4\&.2" "The gitinspector Manual"
.\" -----------------------------------------------------------------
.\" * Define some portability stuff
.\" -----------------------------------------------------------------
@ -36,7 +36,7 @@ gitinspector \- statistical analysis tool for git repositories
.sp
Analyze and gather statistics about a git repository\&. The defaut analysis shows general statistics per author, which can be complemented with a timeline analysis that shows the workload and activity of each author\&. Under normal operation, gitinspector filters the results to only show statistics about a number of given extensions and by default only includes source files in the statistical analysis\&.
.sp
Several output formats are supported, including plain text, HTML and XML\&.
Several output formats are supported, including plain text, HTML, JSON and XML\&.
.SH "OPTIONS"
.sp
List information about the repository in REPOSITORY\&. If no repository is specified, the current directory is used\&. If multiple repositories are given, information will be fetched from the last repository specified\&.
@ -45,14 +45,18 @@ Mandatory arguments to long options are mandatory for short options too\&. Boole
.PP
\fB\-f, \-\-file\-types\fR=EXTENSIONS
.RS 4
A comma separated list of file extensions to include when computing statistics\&. The default extensions used are: java,c,cc,cpp,h,hh,hpp,py,glsl,rb,js,sql
A comma separated list of file extensions to include when computing statistics\&. The default extensions used are: java,c,cc,cpp,h,hh,hpp,py,glsl,rb,js,sql\&. Specifying a single
\fI*\fR
asterisk character includes files with no extension\&. Specifying two consecutive
\fI**\fR
asterisk characters includes all files regardless of extension\&.
.RE
.PP
\fB\-F, \-\-format\fR=FORMAT
.RS 4
Defines in which format output should be generated; the default format is
\fItext\fR
and the available formats are: html,htmlembedded,text,xml (see
and the available formats are: html,htmlembedded,json,text,xml (see
\fBOUTPUT FORMATS\fR)
.RE
.PP
@ -124,11 +128,11 @@ Output version information and exit
.RE
.SH "OUTPUT FORMATS"
.sp
There are support for multiple output formats in gitinspector\&. They can be selected using the \fB\-F\fR/\fB\-\-format\fR flags when running the main gitinspector script\&.
There is support for multiple output formats in gitinspector\&. They can be selected using the \fB\-F\fR/\fB\-\-format\fR flags when running the main gitinspector script\&.
.PP
\fBtext (plain text)\fR
.RS 4
Plain text with some very simple ANSI formatting, suitable for console output\&. This is the format chosen by default by gitinspector\&.
Plain text with some very simple ANSI formatting, suitable for console output\&. This is the format chosen by default in gitinspector\&.
.RE
.PP
\fBhtml\fR
@ -141,9 +145,14 @@ HTML with external links\&. The generated HTML page links to some external resou
HTML with no external links\&. Similar to the HTML output format, but requires no active internet connection\&. As a consequence; the generated pages are bigger (as certain scripts have to be embedded into the generated output)\&.
.RE
.PP
\fBjson\fR
.RS 4
JSON suitable for machine consumption\&. If you want to parse the output generated by gitinspector in a script or application of your own; this format is suitable\&.
.RE
.PP
\fBxml\fR
.RS 4
XML suitable for machine consumption\&. If you want to parse the output generated by gitinspector in a script or application of your own; this is the format you should choose\&.
XML suitable for machine consumption\&. If you want to parse the output generated by gitinspector in a script or application of your own; this format is suitable\&.
.RE
.SH "FILTERING"
.sp
@ -204,6 +213,17 @@ gitinspector offers several different ways of filtering out unwanted information
\fBgitinspector \-x revision:8755fb33\fR, filter out and exclude statistics from all revisions containing the hash "8755fb33"
.RE
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
\fBgitinspector \-x message:BUGFIX\fR, filter out and exclude statistics from all revisions containing "BUGFIX" in the commit message\&.
.RE
.sp
The gitinspector command also lets you add multiple filtering rules by simply specifying the \-x options several times or by separating each filtering rule with a comma;
.sp
.RS 4
@ -264,7 +284,7 @@ Sometimes, sub\-string matching (as described above) is simply not enough\&. The
.RE
.SH "USING GIT TO CONFIGURE GITINSPECTOR"
.sp
Options in gitinspector can be set using \fBgit config\fR\&. Consequently, it is possible to configure gitinspector behavior globally (in all git repositories) or locally (in a specific git repository)\&. It also means that settings will be permanently stored\&. All the long options that can be given to gitinspector can also be configure via git config (and take the same arguments)\&.
Options in gitinspector can be set using \fBgit config\fR\&. Consequently, it is possible to configure gitinspector behavior globally (in all git repositories) or locally (in a specific git repository)\&. It also means that settings will be permanently stored\&. All the long options that can be given to gitinspector can also be configured via git config (and take the same arguments)\&.
.sp
To configure how gitinspector should behave in all git repositories, execute the following git command:
.sp
@ -283,6 +303,8 @@ Report gitinspector bugs to gitinspector@ejwa\&.se
The gitinspector project page: https://github\&.com/ejwa/gitinspector
.sp
If you encounter problems, be sure to read the FAQ first: https://github\&.com/ejwa/gitinspector/wiki/FAQ
.sp
There is also an issue tracker at: https://github\&.com/ejwa/gitinspector/issues
.SH "COPYRIGHT"
.sp
Copyright \(co 2012\-2015 Ejwa Software\&. All rights reserved\&. License GPLv3+: GNU GPL version 3 or later http://gnu\&.org/licenses/gpl\&.html\&. This is free software: you are free to change and redistribute it\&. There is NO WARRANTY, to the extent permitted by law\&.

View File

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title>gitinspector</title><link rel="stylesheet" type="text/css" href="docbook-xsl.css" /><meta name="generator" content="DocBook XSL Stylesheets V1.78.1" /></head><body><div xml:lang="en" class="refentry" lang="en"><a id="idp35451520"></a><div class="titlepage"></div><div class="refnamediv"><h2>Name</h2><p>gitinspector — statistical analysis tool for git repositories</p></div><div class="refsynopsisdiv"><a id="_synopsis"></a><h2>Synopsis</h2><p><span class="strong"><strong>gitinspector</strong></span> [OPTION]… [REPOSITORY]</p></div><div class="refsect1"><a id="_description"></a><h2>DESCRIPTION</h2><p>Analyze and gather statistics about a git repository. The defaut analysis shows general statistics per author, which can be complemented with a timeline analysis that shows the workload and activity of each author. Under normal operation, gitinspector filters the results to only show statistics about a number of given extensions and by default only includes source files in the statistical analysis.</p><p>Several output formats are supported, including plain text, HTML and XML.</p></div><div class="refsect1"><a id="_options"></a><h2>OPTIONS</h2><p>List information about the repository in REPOSITORY. If no repository is specified, the current directory is used. If multiple repositories are given, information will be fetched from the last repository specified.</p><p>Mandatory arguments to long options are mandatory for short options too. Boolean arguments can only be given to long options.</p><div class="variablelist"><dl class="variablelist"><dt><span class="term">
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title>gitinspector</title><link rel="stylesheet" type="text/css" href="docbook-xsl.css" /><meta name="generator" content="DocBook XSL Stylesheets V1.78.1" /></head><body><div xml:lang="en" class="refentry" lang="en"><a id="idp53245840"></a><div class="titlepage"></div><div class="refnamediv"><h2>Name</h2><p>gitinspector — statistical analysis tool for git repositories</p></div><div class="refsynopsisdiv"><a id="_synopsis"></a><h2>Synopsis</h2><p><span class="strong"><strong>gitinspector</strong></span> [OPTION]… [REPOSITORY]</p></div><div class="refsect1"><a id="_description"></a><h2>DESCRIPTION</h2><p>Analyze and gather statistics about a git repository. The defaut analysis shows general statistics per author, which can be complemented with a timeline analysis that shows the workload and activity of each author. Under normal operation, gitinspector filters the results to only show statistics about a number of given extensions and by default only includes source files in the statistical analysis.</p><p>Several output formats are supported, including plain text, HTML, JSON and XML.</p></div><div class="refsect1"><a id="_options"></a><h2>OPTIONS</h2><p>List information about the repository in REPOSITORY. If no repository is specified, the current directory is used. If multiple repositories are given, information will be fetched from the last repository specified.</p><p>Mandatory arguments to long options are mandatory for short options too. Boolean arguments can only be given to long options.</p><div class="variablelist"><dl class="variablelist"><dt><span class="term">
<span class="strong"><strong>-f, --file-types</strong></span>=EXTENSIONS
</span></dt><dd>
A comma separated list of file extensions to include when computing statistics. The default extensions used are: java,c,cc,cpp,h,hh,hpp,py,glsl,rb,js,sql
A comma separated list of file extensions to include when computing statistics. The default extensions used are: java,c,cc,cpp,h,hh,hpp,py,glsl,rb,js,sql. Specifying a single <span class="emphasis"><em>*</em></span> asterisk character includes files with no extension. Specifying two consecutive <span class="emphasis"><em>**</em></span> asterisk characters includes all files regardless of extension.
</dd><dt><span class="term">
<span class="strong"><strong>-F, --format</strong></span>=FORMAT
</span></dt><dd>
Defines in which format output should be generated; the default format is <span class="emphasis"><em>text</em></span> and the available formats are: html,htmlembedded,text,xml (see <a class="link" href="#X1" title="OUTPUT FORMATS"><span class="strong"><strong>OUTPUT FORMATS</strong></span></a>)
Defines in which format output should be generated; the default format is <span class="emphasis"><em>text</em></span> and the available formats are: html,htmlembedded,json,text,xml (see <a class="link" href="#X1" title="OUTPUT FORMATS"><span class="strong"><strong>OUTPUT FORMATS</strong></span></a>)
</dd><dt><span class="term">
<span class="strong"><strong>--grading</strong></span>[=BOOL]
</span></dt><dd>
@ -59,10 +59,10 @@
<span class="strong"><strong>--version</strong></span>
</span></dt><dd>
Output version information and exit
</dd></dl></div></div><div class="refsect1"><a id="X1"></a><h2>OUTPUT FORMATS</h2><p>There are support for multiple output formats in gitinspector. They can be selected using the <span class="strong"><strong>-F</strong></span>/<span class="strong"><strong>--format</strong></span> flags when running the main gitinspector script.</p><div class="variablelist"><dl class="variablelist"><dt><span class="term">
</dd></dl></div></div><div class="refsect1"><a id="X1"></a><h2>OUTPUT FORMATS</h2><p>There is support for multiple output formats in gitinspector. They can be selected using the <span class="strong"><strong>-F</strong></span>/<span class="strong"><strong>--format</strong></span> flags when running the main gitinspector script.</p><div class="variablelist"><dl class="variablelist"><dt><span class="term">
<span class="strong"><strong>text (plain text)</strong></span>
</span></dt><dd>
Plain text with some very simple ANSI formatting, suitable for console output. This is the format chosen by default by gitinspector.
Plain text with some very simple ANSI formatting, suitable for console output. This is the format chosen by default in gitinspector.
</dd><dt><span class="term">
<span class="strong"><strong>html</strong></span>
</span></dt><dd>
@ -72,9 +72,13 @@
</span></dt><dd>
HTML with no external links. Similar to the HTML output format, but requires no active internet connection. As a consequence; the generated pages are bigger (as certain scripts have to be embedded into the generated output).
</dd><dt><span class="term">
<span class="strong"><strong>json</strong></span>
</span></dt><dd>
JSON suitable for machine consumption. If you want to parse the output generated by gitinspector in a script or application of your own; this format is suitable.
</dd><dt><span class="term">
<span class="strong"><strong>xml</strong></span>
</span></dt><dd>
XML suitable for machine consumption. If you want to parse the output generated by gitinspector in a script or application of your own; this is the format you should choose.
XML suitable for machine consumption. If you want to parse the output generated by gitinspector in a script or application of your own; this format is suitable.
</dd></dl></div></div><div class="refsect1"><a id="X2"></a><h2>FILTERING</h2><p>gitinspector offers several different ways of filtering out unwanted information from the generated statistics:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">
<span class="strong"><strong>gitinspector -x myfile</strong></span>, filter out and exclude statistics from all files (or paths) with the string "myfile"
</li><li class="listitem">
@ -85,6 +89,8 @@
<span class="strong"><strong>gitinspector -x email:@gmail.com</strong></span>, filter out and exclude statistics from all authors with a gmail account
</li><li class="listitem">
<span class="strong"><strong>gitinspector -x revision:8755fb33</strong></span>, filter out and exclude statistics from all revisions containing the hash "8755fb33"
</li><li class="listitem">
<span class="strong"><strong>gitinspector -x message:BUGFIX</strong></span>, filter out and exclude statistics from all revisions containing "BUGFIX" in the commit message.
</li></ul></div><p>The gitinspector command also lets you add multiple filtering rules by simply specifying the -x options several times or by separating each filtering rule with a comma;</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">
<span class="strong"><strong>gitinspector -x author:John -x email:@gmail.com</strong></span>
</li><li class="listitem">
@ -95,5 +101,5 @@
<span class="strong"><strong>gitinspector -x "author:\^(?!([A-C]))"</strong></span>, only show statistics from authors starting with the letters A/B/C
</li><li class="listitem">
<span class="strong"><strong>gitinspector -x "email:.com$"</strong></span>, filter out statistics from all email addresses ending with ".com"
</li></ul></div></div><div class="refsect1"><a id="_using_git_to_configure_gitinspector"></a><h2>USING GIT TO CONFIGURE GITINSPECTOR</h2><p>Options in gitinspector can be set using <span class="strong"><strong>git config</strong></span>. Consequently, it is possible to configure gitinspector behavior globally (in all git repositories) or locally (in a specific git repository). It also means that settings will be permanently stored. All the long options that can be given to gitinspector can also be configure via git config (and take the same arguments).</p><p>To configure how gitinspector should behave in all git repositories, execute the following git command:</p><p><span class="strong"><strong>git config --global inspector.option setting</strong></span></p><p>To configure how gitinspector should behave in a specific git repository, execute the following git command (with the current directory standing inside the repository in question):</p><p><span class="strong"><strong>git config inspector.option setting</strong></span></p></div><div class="refsect1"><a id="_author"></a><h2>AUTHOR</h2><p>Originally written by Adam Waldenberg.</p></div><div class="refsect1"><a id="_reporting_bugs"></a><h2>REPORTING BUGS</h2><p>Report gitinspector bugs to <a class="ulink" href="mailto:gitinspector@ejwa.se" target="_top">gitinspector@ejwa.se</a></p><p>The gitinspector project page: <a class="ulink" href="https://github.com/ejwa/gitinspector" target="_top">https://github.com/ejwa/gitinspector</a></p><p>If you encounter problems, be sure to read the FAQ first: <a class="ulink" href="https://github.com/ejwa/gitinspector/wiki/FAQ" target="_top">https://github.com/ejwa/gitinspector/wiki/FAQ</a></p></div><div class="refsect1"><a id="_copyright"></a><h2>COPYRIGHT</h2><p>Copyright © 2012-2015 Ejwa Software. All rights reserved. License GPLv3+: GNU GPL version 3 or later <a class="ulink" href="http://gnu.org/licenses/gpl.html" target="_top">http://gnu.org/licenses/gpl.html</a>.
</li></ul></div></div><div class="refsect1"><a id="_using_git_to_configure_gitinspector"></a><h2>USING GIT TO CONFIGURE GITINSPECTOR</h2><p>Options in gitinspector can be set using <span class="strong"><strong>git config</strong></span>. Consequently, it is possible to configure gitinspector behavior globally (in all git repositories) or locally (in a specific git repository). It also means that settings will be permanently stored. All the long options that can be given to gitinspector can also be configured via git config (and take the same arguments).</p><p>To configure how gitinspector should behave in all git repositories, execute the following git command:</p><p><span class="strong"><strong>git config --global inspector.option setting</strong></span></p><p>To configure how gitinspector should behave in a specific git repository, execute the following git command (with the current directory standing inside the repository in question):</p><p><span class="strong"><strong>git config inspector.option setting</strong></span></p></div><div class="refsect1"><a id="_author"></a><h2>AUTHOR</h2><p>Originally written by Adam Waldenberg.</p></div><div class="refsect1"><a id="_reporting_bugs"></a><h2>REPORTING BUGS</h2><p>Report gitinspector bugs to <a class="ulink" href="mailto:gitinspector@ejwa.se" target="_top">gitinspector@ejwa.se</a></p><p>The gitinspector project page: <a class="ulink" href="https://github.com/ejwa/gitinspector" target="_top">https://github.com/ejwa/gitinspector</a></p><p>If you encounter problems, be sure to read the FAQ first: <a class="ulink" href="https://github.com/ejwa/gitinspector/wiki/FAQ" target="_top">https://github.com/ejwa/gitinspector/wiki/FAQ</a></p><p>There is also an issue tracker at: <a class="ulink" href="https://github.com/ejwa/gitinspector/issues" target="_top">https://github.com/ejwa/gitinspector/issues</a></p></div><div class="refsect1"><a id="_copyright"></a><h2>COPYRIGHT</h2><p>Copyright © 2012-2015 Ejwa Software. All rights reserved. License GPLv3+: GNU GPL version 3 or later <a class="ulink" href="http://gnu.org/licenses/gpl.html" target="_top">http://gnu.org/licenses/gpl.html</a>.
This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law.</p></div><div class="refsect1"><a id="_see_also"></a><h2>SEE ALSO</h2><p><span class="strong"><strong>git</strong></span>(1)</p></div></div></body></html>

Binary file not shown.

View File

@ -3,7 +3,7 @@
GITINSPECTOR(1)
===============
:doctype: manpage
:man version: 0.4.0
:man version: 0.4.2
:man source: gitinspector
:man manual: The gitinspector Manual
@ -20,9 +20,9 @@ SYNOPSIS
DESCRIPTION
-----------
Analyze and gather statistics about a git repository. The defaut analysis shows general statistics per author, which can be complemented with a timeline analysis that shows the workload and activity of each author. Under normal operation, gitinspector filters the results to only show statistics about a number of given extensions and by default only includes source files in the statistical analysis.
Analyze and gather statistics about a git repository. The default analysis shows general statistics per author, which can be complemented with a timeline analysis that shows the workload and activity of each author. Under normal operation, gitinspector filters the results to only show statistics about a number of given extensions and by default only includes source files in the statistical analysis.
Several output formats are supported, including plain text, HTML and XML.
Several output formats are supported, including plain text, HTML, JSON and XML.
OPTIONS
@ -32,10 +32,10 @@ List information about the repository in REPOSITORY. If no repository is specifi
Mandatory arguments to long options are mandatory for short options too. Boolean arguments can only be given to long options.
*-f, --file-types*=EXTENSIONS::
A comma separated list of file extensions to include when computing statistics. The default extensions used are: java,c,cc,cpp,h,hh,hpp,py,glsl,rb,js,sql
A comma separated list of file extensions to include when computing statistics. The default extensions used are: java,c,cc,cpp,h,hh,hpp,py,glsl,rb,js,sql. Specifying a single '\*' asterisk character includes files with no extension. Specifying two consecutive '**' asterisk characters includes all files regardless of extension.
*-F, --format*=FORMAT::
Defines in which format output should be generated; the default format is 'text' and the available formats are: html,htmlembedded,text,xml (see <<X1,*OUTPUT FORMATS*>>)
Defines in which format output should be generated; the default format is 'text' and the available formats are: html,htmlembedded,json,text,xml (see <<X1,*OUTPUT FORMATS*>>)
*--grading*[=BOOL]::
Show statistics and information in a way that is formatted for grading of student projects; this is the same as supplying the options *-HlmrTw*
@ -80,10 +80,10 @@ Mandatory arguments to long options are mandatory for short options too. Boolean
[[X1]]
OUTPUT FORMATS
--------------
There are support for multiple output formats in gitinspector. They can be selected using the *-F*/*--format* flags when running the main gitinspector script.
There is support for multiple output formats in gitinspector. They can be selected using the *-F*/*--format* flags when running the main gitinspector script.
*text (plain text)*::
Plain text with some very simple ANSI formatting, suitable for console output. This is the format chosen by default by gitinspector.
Plain text with some very simple ANSI formatting, suitable for console output. This is the format chosen by default in gitinspector.
*html*::
HTML with external links. The generated HTML page links to some external resources; such as the JavaScript library JQuery. It requires an active internet connection to properly function. This output format will most likely also link to additional external resources in the future.
@ -91,8 +91,11 @@ There are support for multiple output formats in gitinspector. They can be selec
*htmlembedded*::
HTML with no external links. Similar to the HTML output format, but requires no active internet connection. As a consequence; the generated pages are bigger (as certain scripts have to be embedded into the generated output).
*json*::
JSON suitable for machine consumption. If you want to parse the output generated by gitinspector in a script or application of your own; this format is suitable.
*xml*::
XML suitable for machine consumption. If you want to parse the output generated by gitinspector in a script or application of your own; this is the format you should choose.
XML suitable for machine consumption. If you want to parse the output generated by gitinspector in a script or application of your own; this format is suitable.
[[X2]]
@ -105,6 +108,7 @@ gitinspector offers several different ways of filtering out unwanted information
* *gitinspector -x author:John*, filter out and exclude statistics from all authors containing the string "John"
* *gitinspector -x email:@gmail.com*, filter out and exclude statistics from all authors with a gmail account
* *gitinspector -x revision:8755fb33*, filter out and exclude statistics from all revisions containing the hash "8755fb33"
* *gitinspector -x message:BUGFIX*, filter out and exclude statistics from all revisions containing "BUGFIX" in the commit message.
The gitinspector command also lets you add multiple filtering rules by simply specifying the -x options several times or by separating each filtering rule with a comma;
@ -120,7 +124,7 @@ Sometimes, sub-string matching (as described above) is simply not enough. Theref
USING GIT TO CONFIGURE GITINSPECTOR
-----------------------------------
Options in gitinspector can be set using *git config*. Consequently, it is possible to configure gitinspector behavior globally (in all git repositories) or locally (in a specific git repository). It also means that settings will be permanently stored. All the long options that can be given to gitinspector can also be configure via git config (and take the same arguments).
Options in gitinspector can be set using *git config*. Consequently, it is possible to configure gitinspector behavior globally (in all git repositories) or locally (in a specific git repository). It also means that settings will be permanently stored. All the long options that can be given to gitinspector can also be configured via git config (and take the same arguments).
To configure how gitinspector should behave in all git repositories, execute the following git command:
@ -144,6 +148,8 @@ The gitinspector project page: <https://github.com/ejwa/gitinspector>
If you encounter problems, be sure to read the FAQ first: <https://github.com/ejwa/gitinspector/wiki/FAQ>
There is also an issue tracker at: <https://github.com/ejwa/gitinspector/issues>
COPYRIGHT
---------
Copyright (C) 2012-2015 Ejwa Software. All rights reserved. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.

29
gitinspector.js Normal file
View File

@ -0,0 +1,29 @@
var PythonShell = require('python-shell');
var options = {
// The main python script is in the same directory as this file
scriptPath: __dirname,
// Get command line arguments, skipping the default node args:
// arg0 == node executable, arg1 == this file
args: process.argv.slice(2)
};
// Set encoding used by stdin etc manually. Without this, gitinspector may fail to run.
process.env.PYTHONIOENCODING = 'utf8';
// Start inspector
var inspector = new PythonShell('gitinspector.py', options);
// Handle stdout
inspector.on('message', function(message) {
console.log(message);
});
// Let the inspector run, catching any error at the end
inspector.end(function (err) {
if (err) {
throw err;
}
});

24
gitinspector.py Executable file
View File

@ -0,0 +1,24 @@
#!/usr/bin/env python
# coding: utf-8
#
# Copyright © 2015 Ejwa Software. All rights reserved.
#
# This file is part of gitinspector.
#
# gitinspector is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# gitinspector is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with gitinspector. If not, see <http://www.gnu.org/licenses/>.
from gitinspector import gitinspector
if __name__ == "__main__":
gitinspector.main()

View File

@ -1,6 +1,6 @@
# coding: utf-8
#
# Copyright © 2012-2014 Ejwa Software. All rights reserved.
# Copyright © 2012-2015 Ejwa Software. All rights reserved.
#
# This file is part of gitinspector.
#
@ -22,36 +22,42 @@ import subprocess
import sys
def get_basedir():
if hasattr(sys,'frozen'): # exists when running via py2exe
if hasattr(sys, "frozen"): # exists when running via py2exe
return sys.prefix
else:
return os.path.dirname(os.path.realpath(__file__))
__git_basedir__ = None
def get_basedir_git(path=None):
previous_directory = None
def get_basedir_git():
global __git_basedir__
if path != None:
previous_directory = os.getcwd()
os.chdir(path)
if not __git_basedir__:
sp = subprocess.Popen(["git", "rev-parse", "--is-bare-repository"], bufsize=1,
stdout=subprocess.PIPE, stderr=open(os.devnull, "w"))
isbare = sp.stdout.readlines()
sp.wait()
if sp.returncode != 0:
sys.exit(_("Error processing git repository at \"%s\"." % os.getcwd()))
isbare = (isbare[0].decode("utf-8", "replace").strip() == "true")
absolute_path = ""
bare_command = subprocess.Popen(["git", "rev-parse", "--is-bare-repository"], bufsize=1,
stdout=subprocess.PIPE, stderr=open(os.devnull, "w"))
if isbare:
absolute_path = subprocess.Popen(["git", "rev-parse", "--git-dir"], bufsize=1, stdout=subprocess.PIPE).stdout
else:
absolute_path = subprocess.Popen(["git", "rev-parse", "--show-toplevel"], bufsize=1,
stdout=subprocess.PIPE).stdout
isbare = bare_command.stdout.readlines()
bare_command.wait()
absolute_path = absolute_path.readlines()
if len(absolute_path) == 0:
sys.exit(_("Unable to determine absolute path of git repository."))
if bare_command.returncode != 0:
sys.exit(_("Error processing git repository at \"%s\"." % os.getcwd()))
__git_basedir__ = absolute_path[0].decode("utf-8", "replace").strip()
isbare = (isbare[0].decode("utf-8", "replace").strip() == "true")
absolute_path = None
return __git_basedir__
if isbare:
absolute_path = subprocess.Popen(["git", "rev-parse", "--git-dir"], bufsize=1, stdout=subprocess.PIPE).stdout
else:
absolute_path = subprocess.Popen(["git", "rev-parse", "--show-toplevel"], bufsize=1,
stdout=subprocess.PIPE).stdout
absolute_path = absolute_path.readlines()
if len(absolute_path) == 0:
sys.exit(_("Unable to determine absolute path of git repository."))
if path != None:
os.chdir(previous_directory)
return absolute_path[0].decode("utf-8", "replace").strip()

View File

@ -1,6 +1,6 @@
# coding: utf-8
#
# Copyright © 2012-2014 Ejwa Software. All rights reserved.
# Copyright © 2012-2017 Ejwa Software. All rights reserved.
#
# This file is part of gitinspector.
#
@ -19,28 +19,18 @@
from __future__ import print_function
from __future__ import unicode_literals
from localization import N_
from outputable import Outputable
from changes import FileDiff
import comment
import changes
import datetime
import filtering
import format
import gravatar
import interval
import json
import multiprocessing
import re
import subprocess
import sys
import terminal
import textwrap
import threading
from .localization import N_
from .changes import FileDiff
from . import comment, extensions, filtering, format, interval, terminal
NUM_THREADS = multiprocessing.cpu_count()
class BlameEntry:
class BlameEntry(object):
rows = 0
skew = 0 # Used when calculating average code age.
comments = 0
@ -77,16 +67,17 @@ class BlameThread(threading.Thread):
if self.blamechunk_is_prior and interval.get_since():
return
try:
author = self.changes.get_latest_author_by_email(self.blamechunk_email)
except KeyError:
return
__blame_lock__.acquire() # Global lock used to protect calls from here...
if not filtering.set_filtered(author, "author") and not filtering.set_filtered(self.blamechunk_email, "email") and not \
if not filtering.set_filtered(author, "author") and not \
filtering.set_filtered(self.blamechunk_email, "email") and not \
filtering.set_filtered(self.blamechunk_revision, "revision"):
__blame_lock__.acquire() # Global lock used to protect calls from here...
if self.blames.get((author, self.filename), None) == None:
self.blames[(author, self.filename)] = BlameEntry()
@ -97,7 +88,7 @@ class BlameThread(threading.Thread):
self.blames[(author, self.filename)].skew += ((self.changes.last_commit_date - self.blamechunk_time).days /
(7.0 if self.useweeks else AVG_DAYS_PER_MONTH))
__blame_lock__.release() # ...to here.
__blame_lock__.release() # ...to here.
def run(self):
git_blame_r = subprocess.Popen(self.blame_command, bufsize=1, stdout=subprocess.PIPE).stdout
@ -106,6 +97,7 @@ class BlameThread(threading.Thread):
self.__clear_blamechunk_info__()
#pylint: disable=W0201
for j in range(0, len(rows)):
row = rows[j].decode("utf-8", "replace").strip()
keyval = row.split(" ", 2)
@ -126,41 +118,54 @@ class BlameThread(threading.Thread):
__thread_lock__.release() # Lock controlling the number of threads running
PROGRESS_TEXT = N_("Checking how many rows belong to each author (Progress): {0:.0f}%")
PROGRESS_TEXT = N_("Checking how many rows belong to each author (2 of 2): {0:.0f}%")
class Blame:
def __init__(self, hard, useweeks, changes):
class Blame(object):
def __init__(self, repo, hard, useweeks, changes):
self.blames = {}
ls_tree_r = subprocess.Popen(["git", "ls-tree", "--name-only", "-r", interval.get_ref()], bufsize=1,
stdout=subprocess.PIPE).stdout
lines = ls_tree_r.readlines()
ls_tree_p = subprocess.Popen(["git", "ls-tree", "--name-only", "-r", interval.get_ref()], bufsize=1,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
lines = ls_tree_p.communicate()[0].splitlines()
ls_tree_p.stdout.close()
for i, row in enumerate(lines):
row = row.strip().decode("unicode_escape", "ignore")
row = row.encode("latin-1", "replace")
row = row.decode("utf-8", "replace").strip("\"").strip("'").strip()
if ls_tree_p.returncode == 0:
progress_text = _(PROGRESS_TEXT)
if FileDiff.is_valid_extension(row) and not filtering.set_filtered(FileDiff.get_filename(row)):
blame_command = filter(None, ["git", "blame", "--line-porcelain", "-w"] + \
(["-C", "-C", "-M"] if hard else []) +
[interval.get_since(), interval.get_ref(), "--", row])
thread = BlameThread(useweeks, changes, blame_command, FileDiff.get_extension(row), self.blames, row.strip())
thread.daemon = True
thread.start()
if repo != None:
progress_text = "[%s] " % repo.name + progress_text
if hard:
Blame.output_progress(i, len(lines))
for i, row in enumerate(lines):
row = row.strip().decode("unicode_escape", "ignore")
row = row.encode("latin-1", "replace")
row = row.decode("utf-8", "replace").strip("\"").strip("'").strip()
# Make sure all threads have completed.
for i in range(0, NUM_THREADS):
__thread_lock__.acquire()
if FileDiff.get_extension(row) in extensions.get_located() and \
FileDiff.is_valid_extension(row) and not filtering.set_filtered(FileDiff.get_filename(row)):
blame_command = filter(None, ["git", "blame", "--line-porcelain", "-w"] + \
(["-C", "-C", "-M"] if hard else []) +
[interval.get_since(), interval.get_ref(), "--", row])
thread = BlameThread(useweeks, changes, blame_command, FileDiff.get_extension(row),
self.blames, row.strip())
thread.daemon = True
thread.start()
@staticmethod
def output_progress(pos, length):
if sys.stdout.isatty() and format.is_interactive_format():
terminal.clear_row()
print(_(PROGRESS_TEXT).format(100 * pos / length), end="")
sys.stdout.flush()
if format.is_interactive_format():
terminal.output_progress(progress_text, i, len(lines))
# Make sure all threads have completed.
for i in range(0, NUM_THREADS):
__thread_lock__.acquire()
# We also have to release them for future use.
for i in range(0, NUM_THREADS):
__thread_lock__.release()
def __iadd__(self, other):
try:
self.blames.update(other.blames)
return self;
except AttributeError:
return other;
@staticmethod
def is_revision(string):
@ -180,7 +185,7 @@ class Blame:
@staticmethod
def get_time(string):
time = re.search(" \(.*?(\d\d\d\d-\d\d-\d\d)", string)
time = re.search(r" \(.*?(\d\d\d\d-\d\d-\d\d)", string)
return time.group(1).strip()
def get_summed_blames(self):
@ -194,116 +199,3 @@ class Blame:
summed_blames[i[0][0]].comments += i[1].comments
return summed_blames
__blame__ = None
def get(hard, useweeks, changes):
global __blame__
if __blame__ == None:
__blame__ = Blame(hard, useweeks, changes)
return __blame__
BLAME_INFO_TEXT = N_("Below are the number of rows from each author that have survived and are still "
"intact in the current revision")
class BlameOutput(Outputable):
def __init__(self, hard, useweeks):
if format.is_interactive_format():
print("")
self.hard = hard
self.useweeks = useweeks
self.changes = changes.get(hard)
get(self.hard, self.useweeks, self.changes)
Outputable.__init__(self)
def output_html(self):
blame_xml = "<div><div class=\"box\">"
blame_xml += "<p>" + _(BLAME_INFO_TEXT) + ".</p><div><table id=\"blame\" class=\"git\">"
blame_xml += "<thead><tr> <th>{0}</th> <th>{1}</th> <th>{2}</th> <th>{3}</th> <th>{4}</th> </tr></thead>".format(
_("Author"), _("Rows"), _("Stability"), _("Age"), _("% in comments"))
blame_xml += "<tbody>"
chart_data = ""
blames = sorted(__blame__.get_summed_blames().items())
total_blames = 0
for i in blames:
total_blames += i[1].rows
for i, entry in enumerate(blames):
work_percentage = str("{0:.2f}".format(100.0 * entry[1].rows / total_blames))
blame_xml += "<tr " + ("class=\"odd\">" if i % 2 == 1 else ">")
if format.get_selected() == "html":
author_email = self.changes.get_latest_email_by_author(entry[0])
blame_xml += "<td><img src=\"{0}\"/>{1}</td>".format(gravatar.get_url(author_email), entry[0])
else:
blame_xml += "<td>" + entry[0] + "</td>"
blame_xml += "<td>" + str(entry[1].rows) + "</td>"
blame_xml += "<td>" + ("{0:.1f}".format(Blame.get_stability(entry[0], entry[1].rows, self.changes)) + "</td>")
blame_xml += "<td>" + "{0:.1f}".format(float(entry[1].skew) / entry[1].rows) + "</td>"
blame_xml += "<td>" + "{0:.2f}".format(100.0 * entry[1].comments / entry[1].rows) + "</td>"
blame_xml += "<td style=\"display: none\">" + work_percentage + "</td>"
blame_xml += "</tr>"
chart_data += "{{label: {0}, data: {1}}}".format(json.dumps(entry[0]), work_percentage)
if blames[-1] != entry:
chart_data += ", "
blame_xml += "<tfoot><tr> <td colspan=\"5\">&nbsp;</td> </tr></tfoot></tbody></table>"
blame_xml += "<div class=\"chart\" id=\"blame_chart\"></div></div>"
blame_xml += "<script type=\"text/javascript\">"
blame_xml += " blame_plot = $.plot($(\"#blame_chart\"), [{0}], {{".format(chart_data)
blame_xml += " series: {"
blame_xml += " pie: {"
blame_xml += " innerRadius: 0.4,"
blame_xml += " show: true,"
blame_xml += " combine: {"
blame_xml += " threshold: 0.01,"
blame_xml += " label: \"" + _("Minor Authors") + "\""
blame_xml += " }"
blame_xml += " }"
blame_xml += " }, grid: {"
blame_xml += " hoverable: true"
blame_xml += " }"
blame_xml += " });"
blame_xml += "</script></div></div>"
print(blame_xml)
def output_text(self):
if sys.stdout.isatty() and format.is_interactive_format():
terminal.clear_row()
print(textwrap.fill(_(BLAME_INFO_TEXT) + ":", width=terminal.get_size()[0]) + "\n")
terminal.printb(terminal.ljust(_("Author"), 21) + terminal.rjust(_("Rows"), 10) + terminal.rjust(_("Stability"), 15) +
terminal.rjust(_("Age"), 13) + terminal.rjust(_("% in comments"), 20))
for i in sorted(__blame__.get_summed_blames().items()):
print(terminal.ljust(i[0], 20)[0:20 - terminal.get_excess_column_count(i[0])], end=" ")
print(str(i[1].rows).rjust(10), end=" ")
print("{0:.1f}".format(Blame.get_stability(i[0], i[1].rows, self.changes)).rjust(14), end=" ")
print("{0:.1f}".format(float(i[1].skew) / i[1].rows).rjust(12), end=" ")
print("{0:.2f}".format(100.0 * i[1].comments / i[1].rows).rjust(19))
def output_xml(self):
message_xml = "\t\t<message>" + _(BLAME_INFO_TEXT) + "</message>\n"
blame_xml = ""
for i in sorted(__blame__.get_summed_blames().items()):
author_email = self.changes.get_latest_email_by_author(i[0])
name_xml = "\t\t\t\t<name>" + i[0] + "</name>\n"
gravatar_xml = "\t\t\t\t<gravatar>" + gravatar.get_url(author_email) + "</gravatar>\n"
rows_xml = "\t\t\t\t<rows>" + str(i[1].rows) + "</rows>\n"
stability_xml = ("\t\t\t\t<stability>" + "{0:.1f}".format(Blame.get_stability(i[0], i[1].rows,
self.changes)) + "</stability>\n")
age_xml = ("\t\t\t\t<age>" + "{0:.1f}".format(float(i[1].skew) / i[1].rows) + "</age>\n")
percentage_in_comments_xml = ("\t\t\t\t<percentage-in-comments>" + "{0:.2f}".format(100.0 * i[1].comments / i[1].rows) +
"</percentage-in-comments>\n")
blame_xml += ("\t\t\t<author>\n" + name_xml + gravatar_xml + rows_xml + stability_xml + age_xml +
percentage_in_comments_xml + "\t\t\t</author>\n")
print("\t<blame>\n" + message_xml + "\t\t<authors>\n" + blame_xml + "\t\t</authors>\n\t</blame>")

View File

@ -1,6 +1,6 @@
# coding: utf-8
#
# Copyright © 2012-2014 Ejwa Software. All rights reserved.
# Copyright © 2012-2017 Ejwa Software. All rights reserved.
#
# This file is part of gitinspector.
#
@ -17,24 +17,24 @@
# You should have received a copy of the GNU General Public License
# along with gitinspector. If not, see <http://www.gnu.org/licenses/>.
from __future__ import print_function
from __future__ import division
from __future__ import unicode_literals
from localization import N_
from outputable import Outputable
import codecs
import bisect
import datetime
import extensions
import filtering
import format
import gravatar
import interval
import json
import multiprocessing
import os
import subprocess
import terminal
import textwrap
import threading
from .localization import N_
from . import extensions, filtering, format, interval, terminal
class FileDiff:
CHANGES_PER_THREAD = 200
NUM_THREADS = multiprocessing.cpu_count()
__thread_lock__ = threading.BoundedSemaphore(NUM_THREADS)
__changes_lock__ = threading.Lock()
class FileDiff(object):
def __init__(self, string):
commit_line = string.split("|")
@ -62,20 +62,24 @@ class FileDiff:
extension = FileDiff.get_extension(string)
for i in extensions.get():
if extension == i:
if (extension == "" and i == "*") or extension == i or i == '**':
return True
return False
class Commit:
class Commit(object):
def __init__(self, string):
self.filediffs = []
commit_line = string.split("|")
if commit_line.__len__() == 4:
self.date = commit_line[0]
self.sha = commit_line[1]
self.author = commit_line[2].strip()
self.email = commit_line[3].strip()
if commit_line.__len__() == 5:
self.timestamp = commit_line[0]
self.date = commit_line[1]
self.sha = commit_line[2]
self.author = commit_line[3].strip()
self.email = commit_line[4].strip()
def __lt__(self, other):
return self.timestamp.__lt__(other.timestamp) # only used for sorting; we just consider the timestamp.
def add_filediff(self, filediff):
self.filediffs.append(filediff)
@ -87,33 +91,50 @@ class Commit:
def get_author_and_email(string):
commit_line = string.split("|")
if commit_line.__len__() == 4:
return (commit_line[2].strip(), commit_line[3].strip())
if commit_line.__len__() == 5:
return (commit_line[3].strip(), commit_line[4].strip())
@staticmethod
def is_commit_line(string):
return string.split("|").__len__() == 4
return string.split("|").__len__() == 5
class AuthorInfo:
class AuthorInfo(object):
email = None
insertions = 0
deletions = 0
commits = 0
class Changes:
authors = {}
authors_dateinfo = {}
authors_by_email = {}
emails_by_author = {}
class ChangesThread(threading.Thread):
def __init__(self, hard, changes, first_hash, second_hash, offset):
__thread_lock__.acquire() # Lock controlling the number of threads running
threading.Thread.__init__(self)
self.hard = hard
self.changes = changes
self.first_hash = first_hash
self.second_hash = second_hash
self.offset = offset
@staticmethod
def create(hard, changes, first_hash, second_hash, offset):
thread = ChangesThread(hard, changes, first_hash, second_hash, offset)
thread.daemon = True
thread.start()
def run(self):
git_log_r = subprocess.Popen(filter(None, ["git", "log", "--reverse", "--pretty=%ct|%cd|%H|%aN|%aE",
"--stat=100000,8192", "--no-merges", "-w", interval.get_since(),
interval.get_until(), "--date=short"] + (["-C", "-C", "-M"] if self.hard else []) +
[self.first_hash + self.second_hash]), bufsize=1, stdout=subprocess.PIPE).stdout
lines = git_log_r.readlines()
git_log_r.close()
def __init__(self, hard):
self.commits = []
git_log_r = subprocess.Popen(filter(None, ["git", "log", "--reverse", "--pretty=%cd|%H|%aN|%aE", "--stat=100000,8192", "--no-merges", "-w",
interval.get_since(), interval.get_until(), "--date=short"] + (["-C", "-C", "-M"] if hard else [])),
bufsize=1, stdout=subprocess.PIPE).stdout
commit = None
found_valid_extension = False
lines = git_log_r.readlines()
is_filtered = False
commits = []
__changes_lock__.acquire() # Global lock used to protect calls from here...
for i in lines:
j = i.strip().decode("unicode_escape", "ignore")
@ -122,19 +143,26 @@ class Changes:
if Commit.is_commit_line(j):
(author, email) = Commit.get_author_and_email(j)
self.emails_by_author[author] = email
self.authors_by_email[email] = author
self.changes.emails_by_author[author] = email
self.changes.authors_by_email[email] = author
if Commit.is_commit_line(j) or i is lines[-1]:
if found_valid_extension:
self.commits.append(commit)
bisect.insort(commits, commit)
found_valid_extension = False
is_filtered = False
commit = Commit(j)
if FileDiff.is_filediff_line(j) and not filtering.set_filtered(FileDiff.get_filename(j)) and not \
filtering.set_filtered(commit.author, "author") and not filtering.set_filtered(commit.email, "email") and not \
filtering.set_filtered(commit.sha, "revision"):
if Commit.is_commit_line(j) and \
(filtering.set_filtered(commit.author, "author") or \
filtering.set_filtered(commit.email, "email") or \
filtering.set_filtered(commit.sha, "revision") or \
filtering.set_filtered(commit.sha, "message")):
is_filtered = True
if FileDiff.is_filediff_line(j) and not \
filtering.set_filtered(FileDiff.get_filename(j)) and not is_filtered:
extensions.add_located(FileDiff.get_extension(j))
if FileDiff.is_valid_extension(j):
@ -142,19 +170,91 @@ class Changes:
filediff = FileDiff(j)
commit.add_filediff(filediff)
if interval.has_interval() and len(self.commits) > 0:
interval.set_ref(self.commits[-1].sha)
self.changes.commits[self.offset // CHANGES_PER_THREAD] = commits
__changes_lock__.release() # ...to here.
__thread_lock__.release() # Lock controlling the number of threads running
PROGRESS_TEXT = N_("Fetching and calculating primary statistics (1 of 2): {0:.0f}%")
class Changes(object):
authors = {}
authors_dateinfo = {}
authors_by_email = {}
emails_by_author = {}
def __init__(self, repo, hard):
self.commits = []
interval.set_ref("HEAD");
git_rev_list_p = subprocess.Popen(filter(None, ["git", "rev-list", "--reverse", "--no-merges",
interval.get_since(), interval.get_until(), "HEAD"]), bufsize=1,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
lines = git_rev_list_p.communicate()[0].splitlines()
git_rev_list_p.stdout.close()
if git_rev_list_p.returncode == 0 and len(lines) > 0:
progress_text = _(PROGRESS_TEXT)
if repo != None:
progress_text = "[%s] " % repo.name + progress_text
chunks = len(lines) // CHANGES_PER_THREAD
self.commits = [None] * (chunks if len(lines) % CHANGES_PER_THREAD == 0 else chunks + 1)
first_hash = ""
for i, entry in enumerate(lines):
if i % CHANGES_PER_THREAD == CHANGES_PER_THREAD - 1:
entry = entry.decode("utf-8", "replace").strip()
second_hash = entry
ChangesThread.create(hard, self, first_hash, second_hash, i)
first_hash = entry + ".."
if format.is_interactive_format():
terminal.output_progress(progress_text, i, len(lines))
else:
if CHANGES_PER_THREAD - 1 != i % CHANGES_PER_THREAD:
entry = entry.decode("utf-8", "replace").strip()
second_hash = entry
ChangesThread.create(hard, self, first_hash, second_hash, i)
# Make sure all threads have completed.
for i in range(0, NUM_THREADS):
__thread_lock__.acquire()
# We also have to release them for future use.
for i in range(0, NUM_THREADS):
__thread_lock__.release()
self.commits = [item for sublist in self.commits for item in sublist]
if len(self.commits) > 0:
if interval.has_interval():
interval.set_ref(self.commits[-1].sha)
self.first_commit_date = datetime.date(int(self.commits[0].date[0:4]), int(self.commits[0].date[5:7]),
int(self.commits[0].date[8:10]))
self.last_commit_date = datetime.date(int(self.commits[-1].date[0:4]), int(self.commits[-1].date[5:7]),
int(self.commits[-1].date[8:10]))
int(self.commits[-1].date[8:10]))
def __iadd__(self, other):
try:
self.authors.update(other.authors)
self.authors_dateinfo.update(other.authors_dateinfo)
self.authors_by_email.update(other.authors_by_email)
self.emails_by_author.update(other.emails_by_author)
for commit in other.commits:
bisect.insort(self.commits, commit)
if not self.commits and not other.commits:
self.commits = []
return self
except AttributeError:
return other
def get_commits(self):
return self.commits
def __modify_authorinfo__(self, authors, key, commit):
@staticmethod
def modify_authorinfo(authors, key, commit):
if authors.get(key, None) == None:
authors[key] = AuthorInfo()
@ -168,157 +268,26 @@ class Changes:
def get_authorinfo_list(self):
if not self.authors:
for i in self.commits:
self.__modify_authorinfo__(self.authors, i.author, i)
Changes.modify_authorinfo(self.authors, i.author, i)
return self.authors
def get_authordateinfo_list(self):
if not self.authors_dateinfo:
for i in self.commits:
self.__modify_authorinfo__(self.authors_dateinfo, (i.date, i.author), i)
Changes.modify_authorinfo(self.authors_dateinfo, (i.date, i.author), i)
return self.authors_dateinfo
def get_latest_author_by_email(self, name):
if not hasattr(name, 'decode'):
if not hasattr(name, "decode"):
name = str.encode(name)
try:
name = name.decode("unicode_escape", "ignore")
except UnicodeEncodeError:
pass
name = name.decode("unicode_escape", "ignore")
return self.authors_by_email[name]
def get_latest_email_by_author(self, name):
return self.emails_by_author[name]
__changes__ = None
def get(hard):
global __changes__
if __changes__ == None:
__changes__ = Changes(hard)
return __changes__
HISTORICAL_INFO_TEXT = N_("The following historical commit information, by author, was found in the repository")
NO_COMMITED_FILES_TEXT = N_("No commited files with the specified extensions were found")
class ChangesOutput(Outputable):
def __init__(self, hard):
self.changes = get(hard)
Outputable.__init__(self)
def output_html(self):
authorinfo_list = self.changes.get_authorinfo_list()
total_changes = 0.0
changes_xml = "<div><div class=\"box\">"
chart_data = ""
for i in authorinfo_list:
total_changes += authorinfo_list.get(i).insertions
total_changes += authorinfo_list.get(i).deletions
if authorinfo_list:
changes_xml += "<p>" + _(HISTORICAL_INFO_TEXT) + ".</p><div><table id=\"changes\" class=\"git\">"
changes_xml += "<thead><tr> <th>{0}</th> <th>{1}</th> <th>{2}</th> <th>{3}</th> <th>{4}</th>".format(
_("Author"), _("Commits"), _("Insertions"), _("Deletions"), _("% of changes"))
changes_xml += "</tr></thead><tbody>"
for i, entry in enumerate(sorted(authorinfo_list)):
authorinfo = authorinfo_list.get(entry)
percentage = 0 if total_changes == 0 else (authorinfo.insertions + authorinfo.deletions) / total_changes * 100
changes_xml += "<tr " + ("class=\"odd\">" if i % 2 == 1 else ">")
if format.get_selected() == "html":
changes_xml += "<td><img src=\"{0}\"/>{1}</td>".format(
gravatar.get_url(self.changes.get_latest_email_by_author(entry)), entry)
else:
changes_xml += "<td>" + entry + "</td>"
changes_xml += "<td>" + str(authorinfo.commits) + "</td>"
changes_xml += "<td>" + str(authorinfo.insertions) + "</td>"
changes_xml += "<td>" + str(authorinfo.deletions) + "</td>"
changes_xml += "<td>" + "{0:.2f}".format(percentage) + "</td>"
changes_xml += "</tr>"
chart_data += "{{label: {0}, data: {1}}}".format(json.dumps(entry), "{0:.2f}".format(percentage))
if sorted(authorinfo_list)[-1] != entry:
chart_data += ", "
changes_xml += ("<tfoot><tr> <td colspan=\"5\">&nbsp;</td> </tr></tfoot></tbody></table>")
changes_xml += "<div class=\"chart\" id=\"changes_chart\"></div></div>"
changes_xml += "<script type=\"text/javascript\">"
changes_xml += " changes_plot = $.plot($(\"#changes_chart\"), [{0}], {{".format(chart_data)
changes_xml += " series: {"
changes_xml += " pie: {"
changes_xml += " innerRadius: 0.4,"
changes_xml += " show: true,"
changes_xml += " combine: {"
changes_xml += " threshold: 0.01,"
changes_xml += " label: \"" + _("Minor Authors") + "\""
changes_xml += " }"
changes_xml += " }"
changes_xml += " }, grid: {"
changes_xml += " hoverable: true"
changes_xml += " }"
changes_xml += " });"
changes_xml += "</script>"
else:
changes_xml += "<p>" + _(NO_COMMITED_FILES_TEXT) + ".</p>"
changes_xml += "</div></div>"
print(changes_xml)
def output_text(self):
authorinfo_list = self.changes.get_authorinfo_list()
total_changes = 0.0
for i in authorinfo_list:
total_changes += authorinfo_list.get(i).insertions
total_changes += authorinfo_list.get(i).deletions
if authorinfo_list:
print(textwrap.fill(_(HISTORICAL_INFO_TEXT) + ":", width=terminal.get_size()[0]) + "\n")
terminal.printb(terminal.ljust(_("Author"), 21) + terminal.rjust(_("Commits"), 13) +
terminal.rjust(_("Insertions"), 14) + terminal.rjust(_("Deletions"), 15) +
terminal.rjust(_("% of changes"), 16))
for i in sorted(authorinfo_list):
authorinfo = authorinfo_list.get(i)
percentage = 0 if total_changes == 0 else (authorinfo.insertions + authorinfo.deletions) / total_changes * 100
print(terminal.ljust(i, 20)[0:20 - terminal.get_excess_column_count(i)], end=" ")
print(str(authorinfo.commits).rjust(13), end=" ")
print(str(authorinfo.insertions).rjust(13), end=" ")
print(str(authorinfo.deletions).rjust(14), end=" ")
print("{0:.2f}".format(percentage).rjust(15))
else:
print(_(NO_COMMITED_FILES_TEXT) + ".")
def output_xml(self):
authorinfo_list = self.changes.get_authorinfo_list()
total_changes = 0.0
for i in authorinfo_list:
total_changes += authorinfo_list.get(i).insertions
total_changes += authorinfo_list.get(i).deletions
if authorinfo_list:
message_xml = "\t\t<message>" + _(HISTORICAL_INFO_TEXT) + "</message>\n"
changes_xml = ""
for i in sorted(authorinfo_list):
authorinfo = authorinfo_list.get(i)
percentage = 0 if total_changes == 0 else (authorinfo.insertions + authorinfo.deletions) / total_changes * 100
name_xml = "\t\t\t\t<name>" + i + "</name>\n"
gravatar_xml = "\t\t\t\t<gravatar>" + gravatar.get_url(self.changes.get_latest_email_by_author(i)) + "</gravatar>\n"
commits_xml = "\t\t\t\t<commits>" + str(authorinfo.commits) + "</commits>\n"
insertions_xml = "\t\t\t\t<insertions>" + str(authorinfo.insertions) + "</insertions>\n"
deletions_xml = "\t\t\t\t<deletions>" + str(authorinfo.deletions) + "</deletions>\n"
percentage_xml = "\t\t\t\t<percentage-of-changes>" + "{0:.2f}".format(percentage) + "</percentage-of-changes>\n"
changes_xml += ("\t\t\t<author>\n" + name_xml + gravatar_xml + commits_xml + insertions_xml +
deletions_xml + percentage_xml + "\t\t\t</author>\n")
print("\t<changes>\n" + message_xml + "\t\t<authors>\n" + changes_xml + "\t\t</authors>\n\t</changes>")
else:
print("\t<changes>\n\t\t<exception>" + _(NO_COMMITED_FILES_TEXT) + "</exception>\n\t</changes>")

View File

@ -18,30 +18,41 @@
# along with gitinspector. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
import os
import shutil
import subprocess
import sys
import tempfile
__cloned_path__ = None
try:
from urllib.parse import urlparse
except:
from urlparse import urlparse
__cloned_paths__ = []
def create(url):
if url.startswith("file://") or url.startswith("git://") or url.startswith("http://") or \
url.startswith("https://") or url.startswith("ssh://"):
global __cloned_path__
class Repository(object):
def __init__(self, name, location):
self.name = name
self.location = location
location = tempfile.mkdtemp(suffix=".gitinspector")
git_clone = subprocess.Popen(["git", "clone", url, location], bufsize=1, stdout=sys.stderr)
parsed_url = urlparse(url)
if parsed_url.scheme == "file" or parsed_url.scheme == "git" or parsed_url.scheme == "http" or \
parsed_url.scheme == "https" or parsed_url.scheme == "ssh":
path = tempfile.mkdtemp(suffix=".gitinspector")
git_clone = subprocess.Popen(["git", "clone", url, path], bufsize=1, stdout=sys.stderr)
git_clone.wait()
if git_clone.returncode != 0:
sys.exit(git_clone.returncode)
__cloned_path__ = location
return location
return url
__cloned_paths__.append(path)
return Repository(os.path.basename(parsed_url.path), path)
return Repository(None, os.path.abspath(url))
def delete():
if __cloned_path__:
shutil.rmtree(__cloned_path__, ignore_errors=True)
for path in __cloned_paths__:
shutil.rmtree(path, ignore_errors=True)

View File

@ -1,6 +1,6 @@
# coding: utf-8
#
# Copyright © 2012-2014 Ejwa Software. All rights reserved.
# Copyright © 2012-2015 Ejwa Software. All rights reserved.
#
# This file is part of gitinspector.
#
@ -19,17 +19,20 @@
from __future__ import unicode_literals
__comment_begining__ = {"java": "/*", "c": "/*", "cc": "/*", "cpp": "/*", "h": "/*", "hh": "/*", "hpp": "/*", "hs": "{-", "html": "<!--",
"php": "/*", "py": "\"\"\"", "glsl": "/*", "rb": "=begin", "js": "/*", "jspx": "<!--", "scala": "/*",
"sql": "/*", "tex": "\\begin{comment}", "xhtml": "<!--", "xml": "<!--", "ml": "(*", "mli": "(*" }
__comment_begining__ = {"java": "/*", "c": "/*", "cc": "/*", "cpp": "/*", "cs": "/*", "h": "/*", "hh": "/*", "hpp": "/*",
"hs": "{-", "html": "<!--", "php": "/*", "py": "\"\"\"", "glsl": "/*", "rb": "=begin", "js": "/*",
"jspx": "<!--", "scala": "/*", "sql": "/*", "tex": "\\begin{comment}", "xhtml": "<!--",
"xml": "<!--", "ml": "(*", "mli": "(*", "go": "/*", "ly": "%{", "ily": "%{"}
__comment_end__ = {"java": "*/", "c": "*/", "cc": "*/", "cpp": "*/", "h": "*/", "hh": "*/", "hpp": "*/", "hs": "-}", "html": "-->",
"php": "/*", "py": "\"\"\"", "glsl": "*/", "rb": "=end", "js": "*/", "jspx": "-->", "scala": "*/",
"sql": "*/", "tex": "\\end{comment}", "xhtml": "-->", "xml": "-->", "ml": "*)", "mli": "*)" }
__comment_end__ = {"java": "*/", "c": "*/", "cc": "*/", "cpp": "*/", "cs": "*/", "h": "*/", "hh": "*/", "hpp": "*/", "hs": "-}",
"html": "-->", "php": "*/", "py": "\"\"\"", "glsl": "*/", "rb": "=end", "js": "*/", "jspx": "-->",
"scala": "*/", "sql": "*/", "tex": "\\end{comment}", "xhtml": "-->", "xml": "-->", "ml": "*)", "mli": "*)",
"go": "*/", "ly": "%}", "ily": "%}"}
__comment__ = {"java": "//", "c": "//", "cc": "//", "cpp": "//", "h": "//", "hh": "//", "hpp": "//", "hs": "--", "pl": "#", "php": "//",
"py": "#", "glsl": "//", "rb": "#", "js": "//", "scala": "//", "sql": "--", "tex": "%", "ada": "--", "ads": "--",
"adb": "--", "pot": "#", "po": "#" }
__comment__ = {"java": "//", "c": "//", "cc": "//", "cpp": "//", "cs": "//", "h": "//", "hh": "//", "hpp": "//", "hs": "--",
"pl": "#", "php": "//", "py": "#", "glsl": "//", "rb": "#", "robot": "#", "rs": "//", "rlib": "//", "js": "//",
"scala": "//", "sql": "--", "tex": "%", "ada": "--", "ads": "--", "adb": "--", "pot": "#", "po": "#", "go": "//",
"ly": "%", "ily": "%"}
__comment_markers_must_be_at_begining__ = {"tex": True}

View File

@ -1,6 +1,6 @@
# coding: utf-8
#
# Copyright © 2013 Ejwa Software. All rights reserved.
# Copyright © 2013-2015 Ejwa Software. All rights reserved.
#
# This file is part of gitinspector.
#
@ -18,73 +18,76 @@
# along with gitinspector. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
import extensions
import filtering
import format
import interval
import optval
import os
import subprocess
from . import extensions, filtering, format, interval, optval
def __read_git_config__(repo, variable):
previous_directory = os.getcwd()
os.chdir(repo)
setting = subprocess.Popen(["git", "config", "inspector." + variable], bufsize=1, stdout=subprocess.PIPE).stdout
os.chdir(previous_directory)
class GitConfig(object):
def __init__(self, run, repo, global_only=False):
self.run = run
self.repo = repo
self.global_only = global_only
try:
setting = setting.readlines()[0]
setting = setting.decode("utf-8", "replace").strip()
except IndexError:
setting = ""
def __read_git_config__(self, variable):
previous_directory = os.getcwd()
os.chdir(self.repo)
setting = subprocess.Popen(filter(None, ["git", "config", "--global" if self.global_only else "",
"inspector." + variable]), bufsize=1, stdout=subprocess.PIPE).stdout
os.chdir(previous_directory)
return setting
try:
setting = setting.readlines()[0]
setting = setting.decode("utf-8", "replace").strip()
except IndexError:
setting = ""
def __read_git_config_bool__(repo, variable):
try:
variable = __read_git_config__(repo, variable)
return optval.get_boolean_argument(False if variable == "" else variable)
except optval.InvalidOptionArgument:
return False
return setting
def __read_git_config_string__(repo, variable):
string = __read_git_config__(repo, variable)
return (True, string) if len(string) > 0 else (False, None)
def __read_git_config_bool__(self, variable):
try:
variable = self.__read_git_config__(variable)
return optval.get_boolean_argument(False if variable == "" else variable)
except optval.InvalidOptionArgument:
return False
def init(run):
var = __read_git_config_string__(run.repo, "file-types")
if var[0]:
extensions.define(var[1])
def __read_git_config_string__(self, variable):
string = self.__read_git_config__(variable)
return (True, string) if len(string) > 0 else (False, None)
var = __read_git_config_string__(run.repo, "exclude")
if var[0]:
filtering.add(var[1])
def read(self):
var = self.__read_git_config_string__("file-types")
if var[0]:
extensions.define(var[1])
var = __read_git_config_string__(run.repo, "format")
if var[0] and not format.select(var[1]):
raise format.InvalidFormatError(_("specified output format not supported."))
var = self.__read_git_config_string__("exclude")
if var[0]:
filtering.add(var[1])
run.hard = __read_git_config_bool__(run.repo, "hard")
run.list_file_types = __read_git_config_bool__(run.repo, "list-file-types")
run.localize_output = __read_git_config_bool__(run.repo, "localize-output")
run.metrics = __read_git_config_bool__(run.repo, "metrics")
run.responsibilities = __read_git_config_bool__(run.repo, "responsibilities")
run.useweeks = __read_git_config_bool__(run.repo, "weeks")
var = self.__read_git_config_string__("format")
if var[0] and not format.select(var[1]):
raise format.InvalidFormatError(_("specified output format not supported."))
var = __read_git_config_string__(run.repo, "since")
if var[0]:
interval.set_since(var[1])
self.run.hard = self.__read_git_config_bool__("hard")
self.run.list_file_types = self.__read_git_config_bool__("list-file-types")
self.run.localize_output = self.__read_git_config_bool__("localize-output")
self.run.metrics = self.__read_git_config_bool__("metrics")
self.run.responsibilities = self.__read_git_config_bool__("responsibilities")
self.run.useweeks = self.__read_git_config_bool__("weeks")
var = __read_git_config_string__(run.repo, "until")
if var[0]:
interval.set_until(var[1])
var = self.__read_git_config_string__("since")
if var[0]:
interval.set_since(var[1])
run.timeline = __read_git_config_bool__(run.repo, "timeline")
var = self.__read_git_config_string__("until")
if var[0]:
interval.set_until(var[1])
if __read_git_config_bool__(run.repo, "grading"):
run.hard = True
run.list_file_types = True
run.metrics = True
run.responsibilities = True
run.timeline = True
run.useweeks = True
self.run.timeline = self.__read_git_config_bool__("timeline")
if self.__read_git_config_bool__("grading"):
self.run.hard = True
self.run.list_file_types = True
self.run.metrics = True
self.run.responsibilities = True
self.run.timeline = True
self.run.useweeks = True

View File

@ -1,6 +1,6 @@
# coding: utf-8
#
# Copyright © 2012-2014 Ejwa Software. All rights reserved.
# Copyright © 2012-2015 Ejwa Software. All rights reserved.
#
# This file is part of gitinspector.
#
@ -17,12 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with gitinspector. If not, see <http://www.gnu.org/licenses/>.
from __future__ import print_function
from __future__ import unicode_literals
from localization import N_
from outputable import Outputable
import terminal
import textwrap
DEFAULT_EXTENSIONS = ["java", "c", "cc", "cpp", "h", "hh", "hpp", "py", "glsl", "rb", "js", "sql"]
@ -37,51 +32,10 @@ def define(string):
__extensions__ = string.split(",")
def add_located(string):
if len(string) > 0:
if len(string) == 0:
__located_extensions__.add("*")
else:
__located_extensions__.add(string)
EXTENSIONS_INFO_TEXT = N_("The extensions below were found in the repository history")
EXTENSIONS_MARKED_TEXT = N_("(extensions used during statistical analysis are marked)")
class Extensions(Outputable):
def output_html(self):
if __located_extensions__:
extensions_xml = "<div><div class=\"box\">"
extensions_xml += "<p>{0} {1}.</p><p>".format(_(EXTENSIONS_INFO_TEXT), _(EXTENSIONS_MARKED_TEXT))
for i in __located_extensions__:
if i in __extensions__:
extensions_xml += "<strong>" + i + "</strong>"
else:
extensions_xml += i
extensions_xml += " "
extensions_xml += "</p></div></div>"
print(extensions_xml)
def output_text(self):
if __located_extensions__:
print("\n" + textwrap.fill("{0} {1}:".format(_(EXTENSIONS_INFO_TEXT), _(EXTENSIONS_MARKED_TEXT)),
width=terminal.get_size()[0]))
for i in __located_extensions__:
if i in __extensions__:
print("[" + terminal.__bold__ + i + terminal.__normal__ + "]", end=" ")
else:
print (i, end=" ")
print("")
def output_xml(self):
if __located_extensions__:
message_xml = "\t\t<message>" + _(EXTENSIONS_INFO_TEXT) + "</message>\n"
used_extensions_xml = ""
unused_extensions_xml = ""
for i in __located_extensions__:
if i in __extensions__:
used_extensions_xml += "\t\t\t<extension>" + i + "</extension>\n"
else:
unused_extensions_xml += "\t\t\t<extension>" + i + "</extension>\n"
print("\t<extensions>\n" + message_xml + "\t\t<used>\n" + used_extensions_xml + "\t\t</used>\n" +
"\t\t<unused>\n" + unused_extensions_xml + "\t\t</unused>\n" + "\t</extensions>")
def get_located():
return __located_extensions__

View File

@ -1,6 +1,6 @@
# coding: utf-8
#
# Copyright © 2012-2014 Ejwa Software. All rights reserved.
# Copyright © 2012-2015 Ejwa Software. All rights reserved.
#
# This file is part of gitinspector.
#
@ -17,15 +17,12 @@
# You should have received a copy of the GNU General Public License
# along with gitinspector. If not, see <http://www.gnu.org/licenses/>.
from __future__ import print_function
from __future__ import unicode_literals
from localization import N_
from outputable import Outputable
import re
import terminal
import textwrap
import subprocess
__filters__ = {"file": [[], set()], "author": [[], set()], "email": [[], set()], "revision": [[], set()]}
__filters__ = {"file": [set(), set()], "author": [set(), set()], "email": [set(), set()], "revision": [set(), set()],
"message" : [set(), None]}
class InvalidRegExpError(ValueError):
def __init__(self, msg):
@ -38,9 +35,9 @@ def get():
def __add_one__(string):
for i in __filters__:
if (i + ":").lower() == string[0:len(i) + 1].lower():
__filters__[i][0].append(string[len(i) + 1:])
__filters__[i][0].add(string[len(i) + 1:])
return
__filters__["file"][0].append(string)
__filters__["file"][0].add(string)
def add(string):
rules = string.split(",")
@ -49,7 +46,7 @@ def add(string):
def clear():
for i in __filters__:
__filters__[i][0] = []
__filters__[i][0] = set()
def get_filered(filter_type="file"):
return __filters__[filter_type][1]
@ -60,83 +57,33 @@ def has_filtered():
return True
return False
def __find_commit_message__(sha):
git_show_r = subprocess.Popen(filter(None, ["git", "show", "-s", "--pretty=%B", "-w", sha]), bufsize=1,
stdout=subprocess.PIPE).stdout
commit_message = git_show_r.read()
git_show_r.close()
commit_message = commit_message.strip().decode("unicode_escape", "ignore")
commit_message = commit_message.encode("latin-1", "replace")
return commit_message.decode("utf-8", "replace")
def set_filtered(string, filter_type="file"):
string = string.strip()
if len(string) > 0:
for i in __filters__[filter_type][0]:
search_for = string
if filter_type == "message":
search_for = __find_commit_message__(string)
try:
if re.search(i, string) != None:
__filters__[filter_type][1].add(string)
if re.search(i, search_for) != None:
if filter_type == "message":
__add_one__("revision:" + string)
else:
__filters__[filter_type][1].add(string)
return True
except:
raise InvalidRegExpError(_("invalid regular expression specified"))
return False
FILTERING_INFO_TEXT = N_("The following files were excluded from the statistics due to the specified exclusion patterns")
FILTERING_AUTHOR_INFO_TEXT = N_("The following authors were excluded from the statistics due to the specified exclusion patterns")
FILTERING_EMAIL_INFO_TEXT = N_("The authors with the following emails were excluded from the statistics due to the specified " \
"exclusion patterns")
FILTERING_EMAIL_INFO_TEXT = N_("The following commit revisions were excluded from the statistics due to the specified " \
"exclusion patterns")
class Filtering(Outputable):
@staticmethod
def __output_html_section__(info_string, filtered):
filtering_xml = ""
if filtered:
filtering_xml += "<p>" + info_string + "."+ "</p>"
for i in filtered:
filtering_xml += "<p>" + i + "</p>"
return filtering_xml
def output_html(self):
if has_filtered():
filtering_xml = "<div><div class=\"box\">"
Filtering.__output_html_section__(_(FILTERING_INFO_TEXT), __filters__["file"][1])
Filtering.__output_html_section__(_(FILTERING_AUTHOR_INFO_TEXT), __filters__["author"][1])
Filtering.__output_html_section__(_(FILTERING_EMAIL_INFO_TEXT), __filters__["email"][1])
Filtering.__output_html_section__(_(FILTERING_EMAIL_INFO_TEXT), __filters__["revision"][1])
filtering_xml += "</div></div>"
print(filtering_xml)
@staticmethod
def __output_text_section__(info_string, filtered):
if filtered:
print("\n" + textwrap.fill(info_string + ":", width=terminal.get_size()[0]))
for i in filtered:
(width, _unused) = terminal.get_size()
print("...%s" % i[-width+3:] if len(i) > width else i)
def output_text(self):
Filtering.__output_text_section__(_(FILTERING_INFO_TEXT), __filters__["file"][1])
Filtering.__output_text_section__(_(FILTERING_AUTHOR_INFO_TEXT), __filters__["author"][1])
Filtering.__output_text_section__(_(FILTERING_EMAIL_INFO_TEXT), __filters__["email"][1])
Filtering.__output_text_section__(_(FILTERING_EMAIL_INFO_TEXT), __filters__["revision"][1])
@staticmethod
def __output_xml_section__(info_string, filtered, container_tagname):
if filtered:
message_xml = "\t\t\t<message>" +info_string + "</message>\n"
filtering_xml = ""
for i in filtered:
filtering_xml += "\t\t\t\t<entry>".format(container_tagname) + i + "</entry>\n".format(container_tagname)
print("\t\t<{0}>".format(container_tagname))
print(message_xml + "\t\t\t<entries>\n" + filtering_xml + "\t\t\t</entries>\n")
print("\t\t</{0}>".format(container_tagname))
def output_xml(self):
if has_filtered():
print("\t<filtering>")
Filtering.__output_xml_section__(_(FILTERING_INFO_TEXT), __filters__["file"][1], "files")
Filtering.__output_xml_section__(_(FILTERING_AUTHOR_INFO_TEXT), __filters__["author"][1], "authors")
Filtering.__output_xml_section__(_(FILTERING_EMAIL_INFO_TEXT), __filters__["email"][1], "emails")
Filtering.__output_xml_section__(_(FILTERING_EMAIL_INFO_TEXT), __filters__["revision"][1].union(), "revisions")
print("\t</filtering>")

View File

@ -19,19 +19,17 @@
from __future__ import print_function
from __future__ import unicode_literals
import localization
import version
import base64
import basedir
import os
import terminal
import textwrap
import time
import zipfile
from .localization import N_
from . import basedir, localization, terminal, version
__available_formats__ = ["html", "htmlembedded", "text", "xml"]
__available_formats__ = ["html", "htmlembedded", "json", "text", "xml"]
DEFAULT_FORMAT = __available_formats__[2]
DEFAULT_FORMAT = __available_formats__[3]
__selected_format__ = DEFAULT_FORMAT
@ -55,19 +53,29 @@ def is_interactive_format():
def __output_html_template__(name):
template_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), name)
file_r = open(template_path, "rb")
return file_r.read().decode("utf-8", "replace")
template = file_r.read().decode("utf-8", "replace")
file_r.close()
return template
def __get_zip_file_content__(name, file_name="/html/flot.zip"):
zip_file = zipfile.ZipFile(basedir.get_basedir() + file_name, "r")
content = zip_file.read(name)
zip_file.close()
return content.decode("utf-8", "replace")
def output_header():
INFO_ONE_REPOSITORY = N_("Statistical information for the repository '{0}' was gathered on {1}.")
INFO_MANY_REPOSITORIES = N_("Statistical information for the repositories '{0}' was gathered on {1}.")
def output_header(repos):
repos_string = ", ".join([repo.name for repo in repos])
if __selected_format__ == "html" or __selected_format__ == "htmlembedded":
base = basedir.get_basedir()
html_header = __output_html_template__(base + "/html/html.header")
tablesorter_js = __get_zip_file_content__("jquery.tablesorter.min.js", "/html/jquery.tablesorter.min.js.zip").encode("latin-1", "replace")
tablesorter_js = __get_zip_file_content__("jquery.tablesorter.min.js",
"/html/jquery.tablesorter.min.js.zip").encode("latin-1", "replace")
tablesorter_js = tablesorter_js.decode("utf-8", "ignore")
flot_js = __get_zip_file_content__("jquery.flot.js")
pie_js = __get_zip_file_content__("jquery.flot.pie.js")
@ -81,38 +89,66 @@ def output_header():
if __selected_format__ == "htmlembedded":
jquery_js = ">" + __get_zip_file_content__("jquery.js")
else:
jquery_js = " src=\"https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js\">"
jquery_js = " src=\"https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js\">"
print(html_header.format(title = _("Repository statistics for {0}").format(os.path.basename(basedir.get_basedir_git())),
jquery = jquery_js,
jquery_tablesorter = tablesorter_js,
jquery_flot = flot_js,
jquery_flot_pie = pie_js,
jquery_flot_resize = resize_js,
logo = logo.decode("utf-8", "replace"),
logo_text = _("The output has been generated by {0} {1}. The statistical analysis tool"
print(html_header.format(title=_("Repository statistics for '{0}'").format(repos_string),
jquery=jquery_js,
jquery_tablesorter=tablesorter_js,
jquery_flot=flot_js,
jquery_flot_pie=pie_js,
jquery_flot_resize=resize_js,
logo=logo.decode("utf-8", "replace"),
logo_text=_("The output has been generated by {0} {1}. The statistical analysis tool"
" for git repositories.").format(
"<a href=\"https://github.com/ejwa/gitinspector\">gitinspector</a>",
version.__version__),
repo_text = _("Statistical information for the repository '{0}' was gathered on {1}.").format(
os.path.basename(basedir. get_basedir_git()), localization.get_date()),
show_minor_authors = _("Show minor authors"),
hide_minor_authors = _("Hide minor authors"),
show_minor_rows = _("Show rows with minor work"),
hide_minor_rows = _("Hide rows with minor work")))
repo_text=_(INFO_ONE_REPOSITORY if len(repos) <= 1 else INFO_MANY_REPOSITORIES).format(
repos_string, localization.get_date()),
show_minor_authors=_("Show minor authors"),
hide_minor_authors=_("Hide minor authors"),
show_minor_rows=_("Show rows with minor work"),
hide_minor_rows=_("Hide rows with minor work")))
elif __selected_format__ == "json":
print("{\n\t\"gitinspector\": {")
print("\t\t\"version\": \"" + version.__version__ + "\",")
if len(repos) <= 1:
print("\t\t\"repository\": \"" + repos_string + "\",")
else:
repos_json = "\t\t\"repositories\": [ "
for repo in repos:
repos_json += "\"" + repo.name + "\", "
print(repos_json[:-2] + " ],")
print("\t\t\"report_date\": \"" + time.strftime("%Y/%m/%d") + "\",")
elif __selected_format__ == "xml":
print("<gitinspector>")
print("\t<version>" + version.__version__ + "</version>")
print("\t<repository>" + os.path.basename(basedir. get_basedir_git()) + "</repository>")
if len(repos) <= 1:
print("\t<repository>" + repos_string + "</repository>")
else:
print("\t<repositories>")
for repo in repos:
print("\t\t<repository>" + repo.name + "</repository>")
print("\t</repositories>")
print("\t<report-date>" + time.strftime("%Y/%m/%d") + "</report-date>")
else:
print(textwrap.fill(_("Statistical information for the repository '{0}' was gathered on {1}.").format(
os.path.basename(basedir.get_basedir_git()), localization.get_date()), width=terminal.get_size()[0]))
print(textwrap.fill(_(INFO_ONE_REPOSITORY if len(repos) <= 1 else INFO_MANY_REPOSITORIES).format(
repos_string, localization.get_date()), width=terminal.get_size()[0]))
def output_footer():
if __selected_format__ == "html" or __selected_format__ == "htmlembedded":
base = basedir.get_basedir()
html_footer = __output_html_template__(base + "/html/html.footer")
print(html_footer)
elif __selected_format__ == "json":
print("\n\t}\n}")
elif __selected_format__ == "xml":
print("</gitinspector>")

182
gitinspector/gitinspector.py Executable file → Normal file
View File

@ -1,7 +1,6 @@
#!/usr/bin/env python
# coding: utf-8
#
# Copyright © 2012-2014 Ejwa Software. All rights reserved.
# Copyright © 2012-2015 Ejwa Software. All rights reserved.
#
# This file is part of gitinspector.
#
@ -20,74 +19,85 @@
from __future__ import print_function
from __future__ import unicode_literals
import atexit
import getopt
import os
import sys
from .blame import Blame
from .changes import Changes
from .config import GitConfig
from .metrics import MetricsLogic
from . import (basedir, clone, extensions, filtering, format, help, interval,
localization, optval, terminal, version)
from .output import outputable
from .output.blameoutput import BlameOutput
from .output.changesoutput import ChangesOutput
from .output.extensionsoutput import ExtensionsOutput
from .output.filteringoutput import FilteringOutput
from .output.metricsoutput import MetricsOutput
from .output.responsibilitiesoutput import ResponsibilitiesOutput
from .output.timelineoutput import TimelineOutput
import localization
localization.init()
import atexit
import basedir
import blame
import changes
import clone
import config
import extensions
import filtering
import format
import help
import interval
import getopt
import metrics
import os
import optval
import outputable
import responsibilities
import sys
import terminal
import timeline
import version
class Runner:
class Runner(object):
def __init__(self):
self.hard = False
self.include_metrics = False
self.list_file_types = False
self.localize_output = False
self.repo = "."
self.responsibilities = False
self.grading = False
self.timeline = False
self.useweeks = False
def output(self):
def process(self, repos):
localization.check_compatibility(version.__version__)
if not self.localize_output:
localization.disable()
terminal.skip_escapes(not sys.stdout.isatty())
terminal.set_stdout_encoding()
previous_directory = os.getcwd()
summed_blames = Blame.__new__(Blame)
summed_changes = Changes.__new__(Changes)
summed_metrics = MetricsLogic.__new__(MetricsLogic)
os.chdir(self.repo)
absolute_path = basedir.get_basedir_git()
os.chdir(absolute_path)
format.output_header()
outputable.output(changes.ChangesOutput(self.hard))
if changes.get(self.hard).get_commits():
outputable.output(blame.BlameOutput(self.hard, self.useweeks))
if self.timeline:
outputable.output(timeline.Timeline(changes.get(self.hard), self.useweeks))
for repo in repos:
os.chdir(repo.location)
repo = repo if len(repos) > 1 else None
changes = Changes(repo, self.hard)
summed_blames += Blame(repo, self.hard, self.useweeks, changes)
summed_changes += changes
if self.include_metrics:
outputable.output(metrics.Metrics())
summed_metrics += MetricsLogic()
if sys.stdout.isatty() and format.is_interactive_format():
terminal.clear_row()
else:
os.chdir(previous_directory)
format.output_header(repos)
outputable.output(ChangesOutput(summed_changes))
if summed_changes.get_commits():
outputable.output(BlameOutput(summed_changes, summed_blames))
if self.timeline:
outputable.output(TimelineOutput(summed_changes, self.useweeks))
if self.include_metrics:
outputable.output(MetricsOutput(summed_metrics))
if self.responsibilities:
outputable.output(responsibilities.ResponsibilitiesOutput(self.hard, self.useweeks))
outputable.output(ResponsibilitiesOutput(summed_changes, summed_blames))
outputable.output(filtering.Filtering())
outputable.output(FilteringOutput())
if self.list_file_types:
outputable.output(extensions.Extensions())
outputable.output(ExtensionsOutput())
format.output_footer()
os.chdir(previous_directory)
@ -97,29 +107,43 @@ def __check_python_version__():
python_version = str(sys.version_info[0]) + "." + str(sys.version_info[1])
sys.exit(_("gitinspector requires at least Python 2.6 to run (version {0} was found).").format(python_version))
def __get_validated_git_repos__(repos_relative):
if not repos_relative:
repos_relative = "."
repos = []
#Try to clone the repos or return the same directory and bail out.
for repo in repos_relative:
cloned_repo = clone.create(repo)
if cloned_repo.name == None:
cloned_repo.location = basedir.get_basedir_git(cloned_repo.location)
cloned_repo.name = os.path.basename(cloned_repo.location)
repos.append(cloned_repo)
return repos
def main():
terminal.check_terminal_encoding()
terminal.set_stdin_encoding()
argv = terminal.convert_command_line_to_utf8()
__run__ = Runner()
run = Runner()
repos = []
try:
__opts__, __args__ = optval.gnu_getopt(argv[1:], "f:F:hHlLmrTwx:", ["exclude=", "file-types=", "format=",
"hard:true", "help", "list-file-types:true",
"localize-output:true", "metrics:true", "responsibilities:true",
"since=", "grading:true", "timeline:true", "until=", "version",
"weeks:true"])
for arg in __args__:
__run__.repo = arg
opts, args = optval.gnu_getopt(argv[1:], "f:F:hHlLmrTwx:", ["exclude=", "file-types=", "format=",
"hard:true", "help", "list-file-types:true", "localize-output:true",
"metrics:true", "responsibilities:true", "since=", "grading:true",
"timeline:true", "until=", "version", "weeks:true"])
repos = __get_validated_git_repos__(set(args))
#Try to clone the repo or return the same directory and bail out.
__run__.repo = clone.create(__run__.repo)
#We need the repo above to be set before we read the git config.
config.init(__run__)
#We need the repos above to be set before we read the git config.
GitConfig(run, repos[-1].location).read()
clear_x_on_next_pass = True
for o, a in __opts__:
for o, a in opts:
if o in("-h", "--help"):
help.output()
sys.exit(0)
@ -129,25 +153,25 @@ def main():
if not format.select(a):
raise format.InvalidFormatError(_("specified output format not supported."))
elif o == "-H":
__run__.hard = True
run.hard = True
elif o == "--hard":
__run__.hard = optval.get_boolean_argument(a)
run.hard = optval.get_boolean_argument(a)
elif o == "-l":
__run__.list_file_types = True
run.list_file_types = True
elif o == "--list-file-types":
__run__.list_file_types = optval.get_boolean_argument(a)
run.list_file_types = optval.get_boolean_argument(a)
elif o == "-L":
__run__.localize_output = True
run.localize_output = True
elif o == "--localize-output":
__run__.localize_output = optval.get_boolean_argument(a)
run.localize_output = optval.get_boolean_argument(a)
elif o == "-m":
__run__.include_metrics = True
run.include_metrics = True
elif o == "--metrics":
__run__.include_metrics = optval.get_boolean_argument(a)
run.include_metrics = optval.get_boolean_argument(a)
elif o == "-r":
__run__.responsibilities = True
run.responsibilities = True
elif o == "--responsibilities":
__run__.responsibilities = optval.get_boolean_argument(a)
run.responsibilities = optval.get_boolean_argument(a)
elif o == "--since":
interval.set_since(a)
elif o == "--version":
@ -155,23 +179,23 @@ def main():
sys.exit(0)
elif o == "--grading":
grading = optval.get_boolean_argument(a)
__run__.include_metrics = grading
__run__.list_file_types = grading
__run__.responsibilities = grading
__run__.grading = grading
__run__.hard = grading
__run__.timeline = grading
__run__.useweeks = grading
run.include_metrics = grading
run.list_file_types = grading
run.responsibilities = grading
run.grading = grading
run.hard = grading
run.timeline = grading
run.useweeks = grading
elif o == "-T":
__run__.timeline = True
run.timeline = True
elif o == "--timeline":
__run__.timeline = optval.get_boolean_argument(a)
run.timeline = optval.get_boolean_argument(a)
elif o == "--until":
interval.set_until(a)
elif o == "-w":
__run__.useweeks = True
run.useweeks = True
elif o == "--weeks":
__run__.useweeks = optval.get_boolean_argument(a)
run.useweeks = optval.get_boolean_argument(a)
elif o in("-x", "--exclude"):
if clear_x_on_next_pass:
clear_x_on_next_pass = False
@ -179,7 +203,7 @@ def main():
filtering.add(a)
__check_python_version__()
__run__.output()
run.process(repos)
except (filtering.InvalidRegExpError, format.InvalidFormatError, optval.InvalidOptionArgument, getopt.error) as exception:
print(sys.argv[0], "\b:", exception.msg, file=sys.stderr)

View File

@ -18,12 +18,14 @@
# along with gitinspector. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
import hashlib
try:
from urllib.parse import urlencode
except:
from urllib import urlencode
import format
import hashlib
from . import format
def get_url(email, size=20):
md5hash = hashlib.md5(email.encode("utf-8").lower().strip()).hexdigest()
@ -32,7 +34,7 @@ def get_url(email, size=20):
if format.get_selected() == "html":
params = {"default": "identicon", "size": size}
elif format.get_selected() == "xml":
elif format.get_selected() == "xml" or format.get_selected() == "json":
params = {"default": "identicon"}
return base_url + "?" + urlencode(params)

View File

@ -1,6 +1,6 @@
# coding: utf-8
#
# Copyright © 2012-2014 Ejwa Software. All rights reserved.
# Copyright © 2012-2015 Ejwa Software. All rights reserved.
#
# This file is part of gitinspector.
#
@ -19,14 +19,15 @@
from __future__ import print_function
from __future__ import unicode_literals
from extensions import DEFAULT_EXTENSIONS
from format import __available_formats__
import sys
from .extensions import DEFAULT_EXTENSIONS
from .format import __available_formats__
__doc__ = _("""Usage: {0} [OPTION]... [REPOSITORY]
__doc__ = _("""Usage: {0} [OPTION]... [REPOSITORY]...
List information about the repository in REPOSITORY. If no repository is
specified, the current directory is used. If multiple repositories are
given, information will be fetched from the last repository specified.
given, information will be merged into a unified statistical report.
Mandatory arguments to long options are mandatory for short options too.
Boolean arguments can only be given to long options.
@ -34,6 +35,8 @@ Boolean arguments can only be given to long options.
include when computing statistics. The
default extensions used are:
{1}
Specifying * includes files with no
extension, while ** includes all files
-F, --format=FORMAT define in which format output should be
generated; the default format is 'text' and
the available formats are:
@ -61,7 +64,8 @@ Boolean arguments can only be given to long options.
-w, --weeks[=BOOL] show all statistical information in weeks
instead of in months
-x, --exclude=PATTERN an exclusion pattern describing the file
paths, revisions, author names or author
paths, revisions, revisions with certain
commit messages, author names or author
emails that should be excluded from the
statistics; can be specified multiple times
-h, --help display this help and exit

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@ -86,15 +86,19 @@
}}).each(function() {{
$(this).addClass("hoverable");
this.innerHTML = "{show_minor_authors} (" + this.hiddenCount + ") &or;";
}}).toggle(function() {{
this.innerHTML = "{hide_minor_authors} (" + this.hiddenCount + ") &and;";
$(this).parent().parent().parent().find("tbody tr").show().each(colorRows);
}}, function() {{
this.innerHTML = "{show_minor_authors} (" + this.hiddenCount + ") &or;";
$(this).parent().parent().parent().find("tbody tr td:last-child").filter(function() {{
return parseFloat(this.innerHTML) < MINOR_AUTHOR_PERCENTAGE;
}}).parent().hide();
$("table.git tbody tr:visible").each(colorRows);
}}).click(function() {{
this.clicked = !this.clicked;
if (this.clicked) {{
this.innerHTML = "{hide_minor_authors} (" + this.hiddenCount + ") &and;";
$(this).parent().parent().parent().find("tbody tr").show().each(colorRows);
}} else {{
this.innerHTML = "{show_minor_authors} (" + this.hiddenCount + ") &or;";
$(this).parent().parent().parent().find("tbody tr td:last-child").filter(function() {{
return parseFloat(this.innerHTML) < MINOR_AUTHOR_PERCENTAGE;
}}).parent().hide();
$("table.git tbody tr:visible").each(colorRows);
}}
}});
filterResponsibilities();
@ -103,12 +107,15 @@
$("div#responsibilities div h3:visible").each(colorRows);
$("div#responsibilities").prepend("<div class=\"button\">{show_minor_authors} (" + hiddenResponsibilitiesCount + ") &or;</div>");
$("div#responsibilities div.button").toggle(function() {{
this.innerHTML = "{hide_minor_authors} (" + hiddenResponsibilitiesCount + ") &and;";
$("div#responsibilities div").show();
}}, function() {{
this.innerHTML = "{show_minor_authors} (" + hiddenResponsibilitiesCount + ") &or;";
filterResponsibilities();
$("div#responsibilities div.button").click(function() {{
this.clicked = !this.clicked;
if (this.clicked) {{
this.innerHTML = "{hide_minor_authors} (" + hiddenResponsibilitiesCount + ") &and;";
$("div#responsibilities div").show();
}} else {{
this.innerHTML = "{show_minor_authors} (" + hiddenResponsibilitiesCount + ") &or;";
filterResponsibilities();
}}
}});
}}
@ -119,13 +126,16 @@
$("div#timeline table.git tbody tr:visible").each(colorRows);
$("div#timeline").prepend("<div class=\"button\">{show_minor_rows} (" + hiddenTimelineCount + ") &or;</div>");
$("div#timeline div.button").toggle(function() {{
this.innerHTML = "{hide_minor_rows} (" + hiddenTimelineCount + ") &and;";
$("div#timeline table.git tbody tr").show().each(colorRows);
}}, function() {{
this.innerHTML = "{show_minor_rows} (" + hiddenTimelineCount + ") &or;";
filterTimeLine();
$("div#timeline table.git tbody tr:visible").each(colorRows);
$("div#timeline div.button").click(function() {{
this.clicked = !this.clicked;
if (this.clicked) {{
this.innerHTML = "{hide_minor_rows} (" + hiddenTimelineCount + ") &and;";
$("div#timeline table.git tbody tr").show().each(colorRows);
}} else {{
this.innerHTML = "{show_minor_rows} (" + hiddenTimelineCount + ") &or;";
filterTimeLine();
$("div#timeline table.git tbody tr:visible").each(colorRows);
}}
}});
}}
@ -352,6 +362,6 @@
</head>
<body>
<div><div class="box logo">
<img src="data:image/png;base64,{logo}" />
<a href="https://github.com/ejwa/gitinspector"><img src="data:image/png;base64,{logo}" /></a>
<p>{repo_text}<br>{logo_text}</p>
</div></div>

View File

@ -1,6 +1,6 @@
# coding: utf-8
#
# Copyright © 2012-2013 Ejwa Software. All rights reserved.
# Copyright © 2012-2015 Ejwa Software. All rights reserved.
#
# This file is part of gitinspector.
#

View File

@ -19,12 +19,13 @@
from __future__ import print_function
from __future__ import unicode_literals
import basedir
import gettext
import locale
import os
import re
import sys
import time
from . import basedir
__enabled__ = False
__installed__ = False
@ -69,6 +70,16 @@ def init():
__installed__ = True
__translation__.install(True)
def check_compatibility(version):
if isinstance(__translation__, gettext.GNUTranslations):
header_pattern = re.compile("^([^:\n]+): *(.*?) *$", re.MULTILINE)
header_entries = dict(header_pattern.findall(_("")))
if header_entries["Project-Id-Version"] != "gitinspector {0}".format(version):
print("WARNING: The translation for your system locale is not up to date with the current gitinspector "
"version. The current maintainer of this locale is {0}.".format(header_entries["Last-Translator"]),
file=sys.stderr)
def get_date():
if __enabled__ and isinstance(__translation__, gettext.GNUTranslations):
date = time.strftime("%x")

View File

@ -1,6 +1,6 @@
# coding: utf-8
#
# Copyright © 2012-2014 Ejwa Software. All rights reserved.
# Copyright © 2012-2017 Ejwa Software. All rights reserved.
#
# This file is part of gitinspector.
#
@ -17,59 +17,69 @@
# You should have received a copy of the GNU General Public License
# along with gitinspector. If not, see <http://www.gnu.org/licenses/>.
from __future__ import print_function
from __future__ import unicode_literals
from localization import N_
from outputable import Outputable
from changes import FileDiff
import comment
import filtering
import interval
import re
import subprocess
from .changes import FileDiff
from . import comment, filtering, interval
__metric_eloc__ = {"java": 500, "c": 500, "cpp": 500, "h": 300, "hpp": 300, "php": 500, "py": 500, "glsl": 1000,
__metric_eloc__ = {"java": 500, "c": 500, "cpp": 500, "cs": 500, "h": 300, "hpp": 300, "php": 500, "py": 500, "glsl": 1000,
"rb": 500, "js": 500, "sql": 1000, "xml": 1000}
__metric_cc_tokens__ = [[["java", "js", "c", "cc", "cpp"], ["else", "for\s+\(.*\)", "if\s+\(.*\)", "case\s+\w+:",
"default:", "while\s+\(.*\)"],
__metric_cc_tokens__ = [[["java", "js", "c", "cc", "cpp"], ["else", r"for\s+\(.*\)", r"if\s+\(.*\)", r"case\s+\w+:",
"default:", r"while\s+\(.*\)"],
["assert", "break", "continue", "return"]],
[["py"], ["^\s+elif .*:$", "^\s+else:$", "^\s+for .*:", "^\s+if .*:$", "^\s+while .*:$"],
["^\s+assert", "break", "continue", "return"]]]
[["cs"], ["else", r"for\s+\(.*\)", r"foreach\s+\(.*\)", r"goto\s+\w+:", r"if\s+\(.*\)", r"case\s+\w+:",
"default:", r"while\s+\(.*\)"],
["assert", "break", "continue", "return"]],
[["py"], [r"^\s+elif .*:$", r"^\s+else:$", r"^\s+for .*:", r"^\s+if .*:$", r"^\s+while .*:$"],
[r"^\s+assert", "break", "continue", "return"]]]
METRIC_CYCLOMATIC_COMPLEXITY_THRESHOLD = 50
METRIC_CYCLOMATIC_COMPLEXITY_DENSITY_THRESHOLD = 0.75
class MetricsLogic:
class MetricsLogic(object):
def __init__(self):
self.eloc = {}
self.cyclomatic_complexity = {}
self.cyclomatic_complexity_density = {}
ls_tree_r = subprocess.Popen(["git", "ls-tree", "--name-only", "-r", interval.get_ref()], bufsize=1,
stdout=subprocess.PIPE).stdout
ls_tree_p = subprocess.Popen(["git", "ls-tree", "--name-only", "-r", interval.get_ref()], bufsize=1,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
lines = ls_tree_p.communicate()[0].splitlines()
ls_tree_p.stdout.close()
for i in ls_tree_r.readlines():
i = i.strip().decode("unicode_escape", "ignore")
i = i.encode("latin-1", "replace")
i = i.decode("utf-8", "replace").strip("\"").strip("'").strip()
if ls_tree_p.returncode == 0:
for i in lines:
i = i.strip().decode("unicode_escape", "ignore")
i = i.encode("latin-1", "replace")
i = i.decode("utf-8", "replace").strip("\"").strip("'").strip()
if FileDiff.is_valid_extension(i) and not filtering.set_filtered(FileDiff.get_filename(i)):
file_r = subprocess.Popen(["git", "show", interval.get_ref() + ":{0}".format(i.strip())],
bufsize=1, stdout=subprocess.PIPE).stdout.readlines()
if FileDiff.is_valid_extension(i) and not filtering.set_filtered(FileDiff.get_filename(i)):
file_r = subprocess.Popen(["git", "show", interval.get_ref() + ":{0}".format(i.strip())],
bufsize=1, stdout=subprocess.PIPE).stdout.readlines()
extension = FileDiff.get_extension(i)
lines = MetricsLogic.get_eloc(file_r, extension)
cycc = MetricsLogic.get_cyclomatic_complexity(file_r, extension)
extension = FileDiff.get_extension(i)
lines = MetricsLogic.get_eloc(file_r, extension)
cycc = MetricsLogic.get_cyclomatic_complexity(file_r, extension)
if __metric_eloc__.get(extension, None) != None and __metric_eloc__[extension] < lines:
self.eloc[i.strip()] = lines
if __metric_eloc__.get(extension, None) != None and __metric_eloc__[extension] < lines:
self.eloc[i.strip()] = lines
if METRIC_CYCLOMATIC_COMPLEXITY_THRESHOLD < cycc:
self.cyclomatic_complexity[i.strip()] = cycc
if METRIC_CYCLOMATIC_COMPLEXITY_THRESHOLD < cycc:
self.cyclomatic_complexity[i.strip()] = cycc
if lines > 0 and METRIC_CYCLOMATIC_COMPLEXITY_DENSITY_THRESHOLD < cycc / float(lines):
self.cyclomatic_complexity_density[i.strip()] = cycc / float(lines)
if lines > 0 and METRIC_CYCLOMATIC_COMPLEXITY_DENSITY_THRESHOLD < cycc / float(lines):
self.cyclomatic_complexity_density[i.strip()] = cycc / float(lines)
def __iadd__(self, other):
try:
self.eloc.update(other.eloc)
self.cyclomatic_complexity.update(other.cyclomatic_complexity)
self.cyclomatic_complexity_density.update(other.cyclomatic_complexity_density)
return self
except AttributeError:
return other;
@staticmethod
def get_cyclomatic_complexity(file_r, extension):
@ -113,103 +123,3 @@ class MetricsLogic:
eloc_counter += 1
return eloc_counter
ELOC_INFO_TEXT = N_("The following files are suspiciously big (in order of severity)")
CYCLOMATIC_COMPLEXITY_TEXT = N_("The following files have an elevated cyclomatic complexity (in order of severity)")
CYCLOMATIC_COMPLEXITY_DENSITY_TEXT = N_("The following files have an elevated cyclomatic complexity density " \
"(in order of severity)")
METRICS_MISSING_INFO_TEXT = N_("No metrics violations were found in the repository")
METRICS_VIOLATION_SCORES = [[1.0, "minimal"], [1.25, "minor"], [1.5, "medium"], [2.0, "bad"], [3.0, "severe"]]
def __get_metrics_score__(ceiling, value):
for i in reversed(METRICS_VIOLATION_SCORES):
if value > ceiling * i[0]:
return i[1]
class Metrics(Outputable):
def output_text(self):
metrics_logic = MetricsLogic()
if not metrics_logic.eloc and not metrics_logic.cyclomatic_complexity and not metrics_logic.cyclomatic_complexity_density:
print("\n" + _(METRICS_MISSING_INFO_TEXT) + ".")
if metrics_logic.eloc:
print("\n" + _(ELOC_INFO_TEXT) + ":")
for i in sorted(set([(j, i) for (i, j) in metrics_logic.eloc.items()]), reverse = True):
print(_("{0} ({1} estimated lines of code)").format(i[1], str(i[0])))
if metrics_logic.cyclomatic_complexity:
print("\n" + _(CYCLOMATIC_COMPLEXITY_TEXT) + ":")
for i in sorted(set([(j, i) for (i, j) in metrics_logic.cyclomatic_complexity.items()]), reverse = True):
print(_("{0} ({1} in cyclomatic complexity)").format(i[1], str(i[0])))
if metrics_logic.cyclomatic_complexity_density:
print("\n" + _(CYCLOMATIC_COMPLEXITY_DENSITY_TEXT) + ":")
for i in sorted(set([(j, i) for (i, j) in metrics_logic.cyclomatic_complexity_density.items()]), reverse = True):
print(_("{0} ({1:.3f} in cyclomatic complexity density)").format(i[1], i[0]))
def output_html(self):
metrics_logic = MetricsLogic()
metrics_xml = "<div><div class=\"box\" id=\"metrics\">"
if not metrics_logic.eloc and not metrics_logic.cyclomatic_complexity and not metrics_logic.cyclomatic_complexity_density:
metrics_xml += "<p>" + _(METRICS_MISSING_INFO_TEXT) + ".</p>"
if metrics_logic.eloc:
metrics_xml += "<div><h4>" + _(ELOC_INFO_TEXT) + ".</h4>"
for num, i in enumerate(sorted(set([(j, i) for (i, j) in metrics_logic.eloc.items()]), reverse = True)):
metrics_xml += "<div class=\"" + __get_metrics_score__(__metric_eloc__[FileDiff.get_extension(i[1])], i[0]) + \
(" odd\">" if num % 2 == 1 else "\">") + \
_("{0} ({1} estimated lines of code)").format(i[1], str(i[0])) + "</div>"
metrics_xml += "</div>"
if metrics_logic.cyclomatic_complexity:
metrics_xml += "<div><h4>" + _(CYCLOMATIC_COMPLEXITY_TEXT) + "</h4>"
for num, i in enumerate(sorted(set([(j, i) for (i, j) in metrics_logic.cyclomatic_complexity.items()]), reverse = True)):
metrics_xml += "<div class=\"" + __get_metrics_score__(METRIC_CYCLOMATIC_COMPLEXITY_THRESHOLD, i[0]) + \
(" odd\">" if num % 2 == 1 else "\">") + \
_("{0} ({1} in cyclomatic complexity)").format(i[1], str(i[0])) + "</div>"
metrics_xml += "</div>"
if metrics_logic.cyclomatic_complexity_density:
metrics_xml += "<div><h4>" + _(CYCLOMATIC_COMPLEXITY_DENSITY_TEXT) + "</h4>"
for num, i in enumerate(sorted(set([(j, i) for (i, j) in metrics_logic.cyclomatic_complexity_density.items()]), reverse = True)):
metrics_xml += "<div class=\"" + __get_metrics_score__(METRIC_CYCLOMATIC_COMPLEXITY_DENSITY_THRESHOLD, i[0]) + \
(" odd\">" if num % 2 == 1 else "\">") + \
_("{0} ({1:.3f} in cyclomatic complexity density)").format(i[1], i[0]) + "</div>"
metrics_xml += "</div>"
metrics_xml += "</div></div>"
print(metrics_xml)
def output_xml(self):
metrics_logic = MetricsLogic()
if not metrics_logic.eloc and not metrics_logic.cyclomatic_complexity and not metrics_logic.cyclomatic_complexity_density:
print("\t<metrics>\n\t\t<message>" + _(METRICS_MISSING_INFO_TEXT) + "</message>\n\t</metrics>")
else:
eloc_xml = ""
if metrics_logic.eloc:
for i in sorted(set([(j, i) for (i, j) in metrics_logic.eloc.items()]), reverse = True):
eloc_xml += "\t\t\t<estimated-lines-of-code>\n"
eloc_xml += "\t\t\t\t<file-name>" + i[1] + "</file-name>\n"
eloc_xml += "\t\t\t\t<value>" + str(i[0]) + "</value>\n"
eloc_xml += "\t\t\t</estimated-lines-of-code>\n"
if metrics_logic.cyclomatic_complexity:
for i in sorted(set([(j, i) for (i, j) in metrics_logic.cyclomatic_complexity.items()]), reverse = True):
eloc_xml += "\t\t\t<cyclomatic-complexity>\n"
eloc_xml += "\t\t\t\t<file-name>" + i[1] + "</file-name>\n"
eloc_xml += "\t\t\t\t<value>" + str(i[0]) + "</value>\n"
eloc_xml += "\t\t\t</cyclomatic-complexity>\n"
if metrics_logic.cyclomatic_complexity_density:
for i in sorted(set([(j, i) for (i, j) in metrics_logic.cyclomatic_complexity_density.items()]), reverse = True):
eloc_xml += "\t\t\t<cyclomatic-complexity-density>\n"
eloc_xml += "\t\t\t\t<file-name>" + i[1] + "</file-name>\n"
eloc_xml += "\t\t\t\t<value>{0:.3f}</value>\n".format(i[0])
eloc_xml += "\t\t\t</cyclomatic-complexity-density>\n"
print("\t<metrics>\n\t\t<violations>\n" + eloc_xml + "\t\t</violations>\n\t</metrics>")

View File

@ -0,0 +1,20 @@
# coding: utf-8
#
# Copyright © 2013 Ejwa Software. All rights reserved.
#
# This file is part of gitinspector.
#
# gitinspector is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# gitinspector is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with gitinspector. If not, see <http://www.gnu.org/licenses/>.
# This file was intentionally left blank.

View File

@ -0,0 +1,154 @@
# coding: utf-8
#
# Copyright © 2012-2015 Ejwa Software. All rights reserved.
#
# This file is part of gitinspector.
#
# gitinspector is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# gitinspector is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with gitinspector. If not, see <http://www.gnu.org/licenses/>.
from __future__ import print_function
from __future__ import unicode_literals
import json
import sys
import textwrap
from ..localization import N_
from .. import format, gravatar, terminal
from ..blame import Blame
from .outputable import Outputable
BLAME_INFO_TEXT = N_("Below are the number of rows from each author that have survived and are still "
"intact in the current revision")
class BlameOutput(Outputable):
def __init__(self, changes, blame):
if format.is_interactive_format():
print("")
self.changes = changes
self.blame = blame
Outputable.__init__(self)
def output_html(self):
blame_xml = "<div><div class=\"box\">"
blame_xml += "<p>" + _(BLAME_INFO_TEXT) + ".</p><div><table id=\"blame\" class=\"git\">"
blame_xml += "<thead><tr> <th>{0}</th> <th>{1}</th> <th>{2}</th> <th>{3}</th> <th>{4}</th> </tr></thead>".format(
_("Author"), _("Rows"), _("Stability"), _("Age"), _("% in comments"))
blame_xml += "<tbody>"
chart_data = ""
blames = sorted(self.blame.get_summed_blames().items())
total_blames = 0
for i in blames:
total_blames += i[1].rows
for i, entry in enumerate(blames):
work_percentage = str("{0:.2f}".format(100.0 * entry[1].rows / total_blames))
blame_xml += "<tr " + ("class=\"odd\">" if i % 2 == 1 else ">")
if format.get_selected() == "html":
author_email = self.changes.get_latest_email_by_author(entry[0])
blame_xml += "<td><img src=\"{0}\"/>{1}</td>".format(gravatar.get_url(author_email), entry[0])
else:
blame_xml += "<td>" + entry[0] + "</td>"
blame_xml += "<td>" + str(entry[1].rows) + "</td>"
blame_xml += "<td>" + ("{0:.1f}".format(Blame.get_stability(entry[0], entry[1].rows, self.changes)) + "</td>")
blame_xml += "<td>" + "{0:.1f}".format(float(entry[1].skew) / entry[1].rows) + "</td>"
blame_xml += "<td>" + "{0:.2f}".format(100.0 * entry[1].comments / entry[1].rows) + "</td>"
blame_xml += "<td style=\"display: none\">" + work_percentage + "</td>"
blame_xml += "</tr>"
chart_data += "{{label: {0}, data: {1}}}".format(json.dumps(entry[0]), work_percentage)
if blames[-1] != entry:
chart_data += ", "
blame_xml += "<tfoot><tr> <td colspan=\"5\">&nbsp;</td> </tr></tfoot></tbody></table>"
blame_xml += "<div class=\"chart\" id=\"blame_chart\"></div></div>"
blame_xml += "<script type=\"text/javascript\">"
blame_xml += " blame_plot = $.plot($(\"#blame_chart\"), [{0}], {{".format(chart_data)
blame_xml += " series: {"
blame_xml += " pie: {"
blame_xml += " innerRadius: 0.4,"
blame_xml += " show: true,"
blame_xml += " combine: {"
blame_xml += " threshold: 0.01,"
blame_xml += " label: \"" + _("Minor Authors") + "\""
blame_xml += " }"
blame_xml += " }"
blame_xml += " }, grid: {"
blame_xml += " hoverable: true"
blame_xml += " }"
blame_xml += " });"
blame_xml += "</script></div></div>"
print(blame_xml)
def output_json(self):
message_json = "\t\t\t\"message\": \"" + _(BLAME_INFO_TEXT) + "\",\n"
blame_json = ""
for i in sorted(self.blame.get_summed_blames().items()):
author_email = self.changes.get_latest_email_by_author(i[0])
name_json = "\t\t\t\t\"name\": \"" + i[0] + "\",\n"
email_json = "\t\t\t\t\"email\": \"" + author_email + "\",\n"
gravatar_json = "\t\t\t\t\"gravatar\": \"" + gravatar.get_url(author_email) + "\",\n"
rows_json = "\t\t\t\t\"rows\": " + str(i[1].rows) + ",\n"
stability_json = ("\t\t\t\t\"stability\": " + "{0:.1f}".format(Blame.get_stability(i[0], i[1].rows,
self.changes)) + ",\n")
age_json = ("\t\t\t\t\"age\": " + "{0:.1f}".format(float(i[1].skew) / i[1].rows) + ",\n")
percentage_in_comments_json = ("\t\t\t\t\"percentage_in_comments\": " +
"{0:.2f}".format(100.0 * i[1].comments / i[1].rows) + "\n")
blame_json += ("{\n" + name_json + email_json + gravatar_json + rows_json + stability_json + age_json +
percentage_in_comments_json + "\t\t\t},")
else:
blame_json = blame_json[:-1]
print(",\n\t\t\"blame\": {\n" + message_json + "\t\t\t\"authors\": [\n\t\t\t" + blame_json + "]\n\t\t}", end="")
def output_text(self):
if sys.stdout.isatty() and format.is_interactive_format():
terminal.clear_row()
print(textwrap.fill(_(BLAME_INFO_TEXT) + ":", width=terminal.get_size()[0]) + "\n")
terminal.printb(terminal.ljust(_("Author"), 21) + terminal.rjust(_("Rows"), 10) + terminal.rjust(_("Stability"), 15) +
terminal.rjust(_("Age"), 13) + terminal.rjust(_("% in comments"), 20))
for i in sorted(self.blame.get_summed_blames().items()):
print(terminal.ljust(i[0], 20)[0:20 - terminal.get_excess_column_count(i[0])], end=" ")
print(str(i[1].rows).rjust(10), end=" ")
print("{0:.1f}".format(Blame.get_stability(i[0], i[1].rows, self.changes)).rjust(14), end=" ")
print("{0:.1f}".format(float(i[1].skew) / i[1].rows).rjust(12), end=" ")
print("{0:.2f}".format(100.0 * i[1].comments / i[1].rows).rjust(19))
def output_xml(self):
message_xml = "\t\t<message>" + _(BLAME_INFO_TEXT) + "</message>\n"
blame_xml = ""
for i in sorted(self.blame.get_summed_blames().items()):
author_email = self.changes.get_latest_email_by_author(i[0])
name_xml = "\t\t\t\t<name>" + i[0] + "</name>\n"
email_xml = "\t\t\t\t<email>" + author_email + "</email>\n"
gravatar_xml = "\t\t\t\t<gravatar>" + gravatar.get_url(author_email) + "</gravatar>\n"
rows_xml = "\t\t\t\t<rows>" + str(i[1].rows) + "</rows>\n"
stability_xml = ("\t\t\t\t<stability>" + "{0:.1f}".format(Blame.get_stability(i[0], i[1].rows,
self.changes)) + "</stability>\n")
age_xml = ("\t\t\t\t<age>" + "{0:.1f}".format(float(i[1].skew) / i[1].rows) + "</age>\n")
percentage_in_comments_xml = ("\t\t\t\t<percentage-in-comments>" + "{0:.2f}".format(100.0 * i[1].comments / i[1].rows) +
"</percentage-in-comments>\n")
blame_xml += ("\t\t\t<author>\n" + name_xml + email_xml + gravatar_xml + rows_xml + stability_xml +
age_xml + percentage_in_comments_xml + "\t\t\t</author>\n")
print("\t<blame>\n" + message_xml + "\t\t<authors>\n" + blame_xml + "\t\t</authors>\n\t</blame>")

View File

@ -0,0 +1,189 @@
# coding: utf-8
#
# Copyright © 2012-2015 Ejwa Software. All rights reserved.
#
# This file is part of gitinspector.
#
# gitinspector is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# gitinspector is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with gitinspector. If not, see <http://www.gnu.org/licenses/>.
from __future__ import print_function
from __future__ import unicode_literals
import json
import textwrap
from ..localization import N_
from .. import format, gravatar, terminal
from .outputable import Outputable
HISTORICAL_INFO_TEXT = N_("The following historical commit information, by author, was found")
NO_COMMITED_FILES_TEXT = N_("No commited files with the specified extensions were found")
class ChangesOutput(Outputable):
def __init__(self, changes):
self.changes = changes
Outputable.__init__(self)
def output_html(self):
authorinfo_list = self.changes.get_authorinfo_list()
total_changes = 0.0
changes_xml = "<div><div class=\"box\">"
chart_data = ""
for i in authorinfo_list:
total_changes += authorinfo_list.get(i).insertions
total_changes += authorinfo_list.get(i).deletions
if authorinfo_list:
changes_xml += "<p>" + _(HISTORICAL_INFO_TEXT) + ".</p><div><table id=\"changes\" class=\"git\">"
changes_xml += "<thead><tr> <th>{0}</th> <th>{1}</th> <th>{2}</th> <th>{3}</th> <th>{4}</th>".format(
_("Author"), _("Commits"), _("Insertions"), _("Deletions"), _("% of changes"))
changes_xml += "</tr></thead><tbody>"
for i, entry in enumerate(sorted(authorinfo_list)):
authorinfo = authorinfo_list.get(entry)
percentage = 0 if total_changes == 0 else (authorinfo.insertions + authorinfo.deletions) / total_changes * 100
changes_xml += "<tr " + ("class=\"odd\">" if i % 2 == 1 else ">")
if format.get_selected() == "html":
changes_xml += "<td><img src=\"{0}\"/>{1}</td>".format(
gravatar.get_url(self.changes.get_latest_email_by_author(entry)), entry)
else:
changes_xml += "<td>" + entry + "</td>"
changes_xml += "<td>" + str(authorinfo.commits) + "</td>"
changes_xml += "<td>" + str(authorinfo.insertions) + "</td>"
changes_xml += "<td>" + str(authorinfo.deletions) + "</td>"
changes_xml += "<td>" + "{0:.2f}".format(percentage) + "</td>"
changes_xml += "</tr>"
chart_data += "{{label: {0}, data: {1}}}".format(json.dumps(entry), "{0:.2f}".format(percentage))
if sorted(authorinfo_list)[-1] != entry:
chart_data += ", "
changes_xml += ("<tfoot><tr> <td colspan=\"5\">&nbsp;</td> </tr></tfoot></tbody></table>")
changes_xml += "<div class=\"chart\" id=\"changes_chart\"></div></div>"
changes_xml += "<script type=\"text/javascript\">"
changes_xml += " changes_plot = $.plot($(\"#changes_chart\"), [{0}], {{".format(chart_data)
changes_xml += " series: {"
changes_xml += " pie: {"
changes_xml += " innerRadius: 0.4,"
changes_xml += " show: true,"
changes_xml += " combine: {"
changes_xml += " threshold: 0.01,"
changes_xml += " label: \"" + _("Minor Authors") + "\""
changes_xml += " }"
changes_xml += " }"
changes_xml += " }, grid: {"
changes_xml += " hoverable: true"
changes_xml += " }"
changes_xml += " });"
changes_xml += "</script>"
else:
changes_xml += "<p>" + _(NO_COMMITED_FILES_TEXT) + ".</p>"
changes_xml += "</div></div>"
print(changes_xml)
def output_json(self):
authorinfo_list = self.changes.get_authorinfo_list()
total_changes = 0.0
for i in authorinfo_list:
total_changes += authorinfo_list.get(i).insertions
total_changes += authorinfo_list.get(i).deletions
if authorinfo_list:
message_json = "\t\t\t\"message\": \"" + _(HISTORICAL_INFO_TEXT) + "\",\n"
changes_json = ""
for i in sorted(authorinfo_list):
author_email = self.changes.get_latest_email_by_author(i)
authorinfo = authorinfo_list.get(i)
percentage = 0 if total_changes == 0 else (authorinfo.insertions + authorinfo.deletions) / total_changes * 100
name_json = "\t\t\t\t\"name\": \"" + i + "\",\n"
email_json = "\t\t\t\t\"email\": \"" + author_email + "\",\n"
gravatar_json = "\t\t\t\t\"gravatar\": \"" + gravatar.get_url(author_email) + "\",\n"
commits_json = "\t\t\t\t\"commits\": " + str(authorinfo.commits) + ",\n"
insertions_json = "\t\t\t\t\"insertions\": " + str(authorinfo.insertions) + ",\n"
deletions_json = "\t\t\t\t\"deletions\": " + str(authorinfo.deletions) + ",\n"
percentage_json = "\t\t\t\t\"percentage_of_changes\": " + "{0:.2f}".format(percentage) + "\n"
changes_json += ("{\n" + name_json + email_json + gravatar_json + commits_json +
insertions_json + deletions_json + percentage_json + "\t\t\t}")
changes_json += ","
else:
changes_json = changes_json[:-1]
print("\t\t\"changes\": {\n" + message_json + "\t\t\t\"authors\": [\n\t\t\t" + changes_json + "]\n\t\t}", end="")
else:
print("\t\t\"exception\": \"" + _(NO_COMMITED_FILES_TEXT) + "\"")
def output_text(self):
authorinfo_list = self.changes.get_authorinfo_list()
total_changes = 0.0
for i in authorinfo_list:
total_changes += authorinfo_list.get(i).insertions
total_changes += authorinfo_list.get(i).deletions
if authorinfo_list:
print(textwrap.fill(_(HISTORICAL_INFO_TEXT) + ":", width=terminal.get_size()[0]) + "\n")
terminal.printb(terminal.ljust(_("Author"), 21) + terminal.rjust(_("Commits"), 13) +
terminal.rjust(_("Insertions"), 14) + terminal.rjust(_("Deletions"), 15) +
terminal.rjust(_("% of changes"), 16))
for i in sorted(authorinfo_list):
authorinfo = authorinfo_list.get(i)
percentage = 0 if total_changes == 0 else (authorinfo.insertions + authorinfo.deletions) / total_changes * 100
print(terminal.ljust(i, 20)[0:20 - terminal.get_excess_column_count(i)], end=" ")
print(str(authorinfo.commits).rjust(13), end=" ")
print(str(authorinfo.insertions).rjust(13), end=" ")
print(str(authorinfo.deletions).rjust(14), end=" ")
print("{0:.2f}".format(percentage).rjust(15))
else:
print(_(NO_COMMITED_FILES_TEXT) + ".")
def output_xml(self):
authorinfo_list = self.changes.get_authorinfo_list()
total_changes = 0.0
for i in authorinfo_list:
total_changes += authorinfo_list.get(i).insertions
total_changes += authorinfo_list.get(i).deletions
if authorinfo_list:
message_xml = "\t\t<message>" + _(HISTORICAL_INFO_TEXT) + "</message>\n"
changes_xml = ""
for i in sorted(authorinfo_list):
author_email = self.changes.get_latest_email_by_author(i)
authorinfo = authorinfo_list.get(i)
percentage = 0 if total_changes == 0 else (authorinfo.insertions + authorinfo.deletions) / total_changes * 100
name_xml = "\t\t\t\t<name>" + i + "</name>\n"
email_xml = "\t\t\t\t<email>" + author_email + "</email>\n"
gravatar_xml = "\t\t\t\t<gravatar>" + gravatar.get_url(author_email) + "</gravatar>\n"
commits_xml = "\t\t\t\t<commits>" + str(authorinfo.commits) + "</commits>\n"
insertions_xml = "\t\t\t\t<insertions>" + str(authorinfo.insertions) + "</insertions>\n"
deletions_xml = "\t\t\t\t<deletions>" + str(authorinfo.deletions) + "</deletions>\n"
percentage_xml = "\t\t\t\t<percentage-of-changes>" + "{0:.2f}".format(percentage) + "</percentage-of-changes>\n"
changes_xml += ("\t\t\t<author>\n" + name_xml + email_xml + gravatar_xml + commits_xml +
insertions_xml + deletions_xml + percentage_xml + "\t\t\t</author>\n")
print("\t<changes>\n" + message_xml + "\t\t<authors>\n" + changes_xml + "\t\t</authors>\n\t</changes>")
else:
print("\t<changes>\n\t\t<exception>" + _(NO_COMMITED_FILES_TEXT) + "</exception>\n\t</changes>")

View File

@ -0,0 +1,97 @@
# coding: utf-8
#
# Copyright © 2012-2015 Ejwa Software. All rights reserved.
#
# This file is part of gitinspector.
#
# gitinspector is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# gitinspector is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with gitinspector. If not, see <http://www.gnu.org/licenses/>.
from __future__ import print_function
from __future__ import unicode_literals
import textwrap
from ..localization import N_
from .. import extensions, terminal
from .outputable import Outputable
EXTENSIONS_INFO_TEXT = N_("The extensions below were found in the repository history")
EXTENSIONS_MARKED_TEXT = N_("(extensions used during statistical analysis are marked)")
class ExtensionsOutput(Outputable):
@staticmethod
def is_marked(extension):
if extension in extensions.__extensions__ or "**" in extensions.__extensions__:
return True
return False
def output_html(self):
if extensions.__located_extensions__:
extensions_xml = "<div><div class=\"box\">"
extensions_xml += "<p>{0} {1}.</p><p>".format(_(EXTENSIONS_INFO_TEXT), _(EXTENSIONS_MARKED_TEXT))
for i in sorted(extensions.__located_extensions__):
if ExtensionsOutput.is_marked(i):
extensions_xml += "<strong>" + i + "</strong>"
else:
extensions_xml += i
extensions_xml += " "
extensions_xml += "</p></div></div>"
print(extensions_xml)
def output_json(self):
if extensions.__located_extensions__:
message_json = "\t\t\t\"message\": \"" + _(EXTENSIONS_INFO_TEXT) + "\",\n"
used_extensions_json = ""
unused_extensions_json = ""
for i in sorted(extensions.__located_extensions__):
if ExtensionsOutput.is_marked(i):
used_extensions_json += "\"" + i + "\", "
else:
unused_extensions_json += "\"" + i + "\", "
used_extensions_json = used_extensions_json[:-2]
unused_extensions_json = unused_extensions_json[:-2]
print(",\n\t\t\"extensions\": {\n" + message_json + "\t\t\t\"used\": [ " + used_extensions_json +
" ],\n\t\t\t\"unused\": [ " + unused_extensions_json + " ]\n" + "\t\t}", end="")
def output_text(self):
if extensions.__located_extensions__:
print("\n" + textwrap.fill("{0} {1}:".format(_(EXTENSIONS_INFO_TEXT), _(EXTENSIONS_MARKED_TEXT)),
width=terminal.get_size()[0]))
for i in sorted(extensions.__located_extensions__):
if ExtensionsOutput.is_marked(i):
print("[" + terminal.__bold__ + i + terminal.__normal__ + "]", end=" ")
else:
print (i, end=" ")
print("")
def output_xml(self):
if extensions.__located_extensions__:
message_xml = "\t\t<message>" + _(EXTENSIONS_INFO_TEXT) + "</message>\n"
used_extensions_xml = ""
unused_extensions_xml = ""
for i in sorted(extensions.__located_extensions__):
if ExtensionsOutput.is_marked(i):
used_extensions_xml += "\t\t\t<extension>" + i + "</extension>\n"
else:
unused_extensions_xml += "\t\t\t<extension>" + i + "</extension>\n"
print("\t<extensions>\n" + message_xml + "\t\t<used>\n" + used_extensions_xml + "\t\t</used>\n" +
"\t\t<unused>\n" + unused_extensions_xml + "\t\t</unused>\n" + "\t</extensions>")

View File

@ -0,0 +1,121 @@
# coding: utf-8
#
# Copyright © 2012-2015 Ejwa Software. All rights reserved.
#
# This file is part of gitinspector.
#
# gitinspector is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# gitinspector is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with gitinspector. If not, see <http://www.gnu.org/licenses/>.
from __future__ import print_function
from __future__ import unicode_literals
import textwrap
from ..localization import N_
from ..filtering import __filters__, has_filtered
from .. import terminal
from .outputable import Outputable
FILTERING_INFO_TEXT = N_("The following files were excluded from the statistics due to the specified exclusion patterns")
FILTERING_AUTHOR_INFO_TEXT = N_("The following authors were excluded from the statistics due to the specified exclusion patterns")
FILTERING_EMAIL_INFO_TEXT = N_("The authors with the following emails were excluded from the statistics due to the specified " \
"exclusion patterns")
FILTERING_COMMIT_INFO_TEXT = N_("The following commit revisions were excluded from the statistics due to the specified " \
"exclusion patterns")
class FilteringOutput(Outputable):
@staticmethod
def __output_html_section__(info_string, filtered):
filtering_xml = ""
if filtered:
filtering_xml += "<p>" + info_string + "."+ "</p>"
for i in filtered:
filtering_xml += "<p>" + i + "</p>"
return filtering_xml
def output_html(self):
if has_filtered():
filtering_xml = "<div><div class=\"box\">"
FilteringOutput.__output_html_section__(_(FILTERING_INFO_TEXT), __filters__["file"][1])
FilteringOutput.__output_html_section__(_(FILTERING_AUTHOR_INFO_TEXT), __filters__["author"][1])
FilteringOutput.__output_html_section__(_(FILTERING_EMAIL_INFO_TEXT), __filters__["email"][1])
FilteringOutput.__output_html_section__(_(FILTERING_COMMIT_INFO_TEXT), __filters__["revision"][1])
filtering_xml += "</div></div>"
print(filtering_xml)
@staticmethod
def __output_json_section__(info_string, filtered, container_tagname):
if filtered:
message_json = "\t\t\t\t\"message\": \"" + info_string + "\",\n"
filtering_json = ""
for i in filtered:
filtering_json += "\t\t\t\t\t\"" + i + "\",\n"
else:
filtering_json = filtering_json[:-3]
return "\n\t\t\t\"{0}\": {{\n".format(container_tagname) + message_json + \
"\t\t\t\t\"entries\": [\n" + filtering_json + "\"\n\t\t\t\t]\n\t\t\t},"
return ""
def output_json(self):
if has_filtered():
output = ",\n\t\t\"filtering\": {"
output += FilteringOutput.__output_json_section__(_(FILTERING_INFO_TEXT), __filters__["file"][1], "files")
output += FilteringOutput.__output_json_section__(_(FILTERING_AUTHOR_INFO_TEXT), __filters__["author"][1], "authors")
output += FilteringOutput.__output_json_section__(_(FILTERING_EMAIL_INFO_TEXT), __filters__["email"][1], "emails")
output += FilteringOutput.__output_json_section__(_(FILTERING_COMMIT_INFO_TEXT), __filters__["revision"][1], "revision")
output = output[:-1]
output += "\n\t\t}"
print(output, end="")
@staticmethod
def __output_text_section__(info_string, filtered):
if filtered:
print("\n" + textwrap.fill(info_string + ":", width=terminal.get_size()[0]))
for i in filtered:
(width, _unused) = terminal.get_size()
print("...%s" % i[-width+3:] if len(i) > width else i)
def output_text(self):
FilteringOutput.__output_text_section__(_(FILTERING_INFO_TEXT), __filters__["file"][1])
FilteringOutput.__output_text_section__(_(FILTERING_AUTHOR_INFO_TEXT), __filters__["author"][1])
FilteringOutput.__output_text_section__(_(FILTERING_EMAIL_INFO_TEXT), __filters__["email"][1])
FilteringOutput.__output_text_section__(_(FILTERING_COMMIT_INFO_TEXT), __filters__["revision"][1])
@staticmethod
def __output_xml_section__(info_string, filtered, container_tagname):
if filtered:
message_xml = "\t\t\t<message>" + info_string + "</message>\n"
filtering_xml = ""
for i in filtered:
filtering_xml += "\t\t\t\t<entry>" + i + "</entry>\n"
print("\t\t<{0}>".format(container_tagname))
print(message_xml + "\t\t\t<entries>\n" + filtering_xml + "\t\t\t</entries>\n")
print("\t\t</{0}>".format(container_tagname))
def output_xml(self):
if has_filtered():
print("\t<filtering>")
FilteringOutput.__output_xml_section__(_(FILTERING_INFO_TEXT), __filters__["file"][1], "files")
FilteringOutput.__output_xml_section__(_(FILTERING_AUTHOR_INFO_TEXT), __filters__["author"][1], "authors")
FilteringOutput.__output_xml_section__(_(FILTERING_EMAIL_INFO_TEXT), __filters__["email"][1], "emails")
FilteringOutput.__output_xml_section__(_(FILTERING_COMMIT_INFO_TEXT), __filters__["revision"][1], "revision")
print("\t</filtering>")

View File

@ -0,0 +1,160 @@
# coding: utf-8
#
# Copyright © 2012-2015 Ejwa Software. All rights reserved.
#
# This file is part of gitinspector.
#
# gitinspector is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# gitinspector is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with gitinspector. If not, see <http://www.gnu.org/licenses/>.
from __future__ import print_function
from __future__ import unicode_literals
from ..changes import FileDiff
from ..localization import N_
from ..metrics import (__metric_eloc__, METRIC_CYCLOMATIC_COMPLEXITY_THRESHOLD, METRIC_CYCLOMATIC_COMPLEXITY_DENSITY_THRESHOLD)
from .outputable import Outputable
ELOC_INFO_TEXT = N_("The following files are suspiciously big (in order of severity)")
CYCLOMATIC_COMPLEXITY_TEXT = N_("The following files have an elevated cyclomatic complexity (in order of severity)")
CYCLOMATIC_COMPLEXITY_DENSITY_TEXT = N_("The following files have an elevated cyclomatic complexity density " \
"(in order of severity)")
METRICS_MISSING_INFO_TEXT = N_("No metrics violations were found in the repository")
METRICS_VIOLATION_SCORES = [[1.0, "minimal"], [1.25, "minor"], [1.5, "medium"], [2.0, "bad"], [3.0, "severe"]]
def __get_metrics_score__(ceiling, value):
for i in reversed(METRICS_VIOLATION_SCORES):
if value > ceiling * i[0]:
return i[1]
class MetricsOutput(Outputable):
def __init__(self, metrics):
self.metrics = metrics
Outputable.__init__(self)
def output_text(self):
if not self.metrics.eloc and not self.metrics.cyclomatic_complexity and not self.metrics.cyclomatic_complexity_density:
print("\n" + _(METRICS_MISSING_INFO_TEXT) + ".")
if self.metrics.eloc:
print("\n" + _(ELOC_INFO_TEXT) + ":")
for i in sorted(set([(j, i) for (i, j) in self.metrics.eloc.items()]), reverse=True):
print(_("{0} ({1} estimated lines of code)").format(i[1], str(i[0])))
if self.metrics.cyclomatic_complexity:
print("\n" + _(CYCLOMATIC_COMPLEXITY_TEXT) + ":")
for i in sorted(set([(j, i) for (i, j) in self.metrics.cyclomatic_complexity.items()]), reverse=True):
print(_("{0} ({1} in cyclomatic complexity)").format(i[1], str(i[0])))
if self.metrics.cyclomatic_complexity_density:
print("\n" + _(CYCLOMATIC_COMPLEXITY_DENSITY_TEXT) + ":")
for i in sorted(set([(j, i) for (i, j) in self.metrics.cyclomatic_complexity_density.items()]), reverse=True):
print(_("{0} ({1:.3f} in cyclomatic complexity density)").format(i[1], i[0]))
def output_html(self):
metrics_xml = "<div><div class=\"box\" id=\"metrics\">"
if not self.metrics.eloc and not self.metrics.cyclomatic_complexity and not self.metrics.cyclomatic_complexity_density:
metrics_xml += "<p>" + _(METRICS_MISSING_INFO_TEXT) + ".</p>"
if self.metrics.eloc:
metrics_xml += "<div><h4>" + _(ELOC_INFO_TEXT) + ".</h4>"
for num, i in enumerate(sorted(set([(j, i) for (i, j) in self.metrics.eloc.items()]), reverse=True)):
metrics_xml += "<div class=\"" + __get_metrics_score__(__metric_eloc__[FileDiff.get_extension(i[1])], i[0]) + \
(" odd\">" if num % 2 == 1 else "\">") + \
_("{0} ({1} estimated lines of code)").format(i[1], str(i[0])) + "</div>"
metrics_xml += "</div>"
if self.metrics.cyclomatic_complexity:
metrics_xml += "<div><h4>" + _(CYCLOMATIC_COMPLEXITY_TEXT) + "</h4>"
for num, i in enumerate(sorted(set([(j, i) for (i, j) in self.metrics.cyclomatic_complexity.items()]), reverse=True)):
metrics_xml += "<div class=\"" + __get_metrics_score__(METRIC_CYCLOMATIC_COMPLEXITY_THRESHOLD, i[0]) + \
(" odd\">" if num % 2 == 1 else "\">") + \
_("{0} ({1} in cyclomatic complexity)").format(i[1], str(i[0])) + "</div>"
metrics_xml += "</div>"
if self.metrics.cyclomatic_complexity_density:
metrics_xml += "<div><h4>" + _(CYCLOMATIC_COMPLEXITY_DENSITY_TEXT) + "</h4>"
for num, i in enumerate(sorted(set([(j, i) for (i, j) in self.metrics.cyclomatic_complexity_density.items()]), reverse=True)):
metrics_xml += "<div class=\"" + __get_metrics_score__(METRIC_CYCLOMATIC_COMPLEXITY_DENSITY_THRESHOLD, i[0]) + \
(" odd\">" if num % 2 == 1 else "\">") + \
_("{0} ({1:.3f} in cyclomatic complexity density)").format(i[1], i[0]) + "</div>"
metrics_xml += "</div>"
metrics_xml += "</div></div>"
print(metrics_xml)
def output_json(self):
if not self.metrics.eloc and not self.metrics.cyclomatic_complexity and not self.metrics.cyclomatic_complexity_density:
print(",\n\t\t\"metrics\": {\n\t\t\t\"message\": \"" + _(METRICS_MISSING_INFO_TEXT) + "\"\n\t\t}", end="")
else:
eloc_json = ""
if self.metrics.eloc:
for i in sorted(set([(j, i) for (i, j) in self.metrics.eloc.items()]), reverse=True):
eloc_json += "{\n\t\t\t\t\"type\": \"estimated-lines-of-code\",\n"
eloc_json += "\t\t\t\t\"file_name\": \"" + i[1] + "\",\n"
eloc_json += "\t\t\t\t\"value\": " + str(i[0]) + "\n"
eloc_json += "\t\t\t},"
else:
if not self.metrics.cyclomatic_complexity:
eloc_json = eloc_json[:-1]
if self.metrics.cyclomatic_complexity:
for i in sorted(set([(j, i) for (i, j) in self.metrics.cyclomatic_complexity.items()]), reverse=True):
eloc_json += "{\n\t\t\t\t\"type\": \"cyclomatic-complexity\",\n"
eloc_json += "\t\t\t\t\"file_name\": \"" + i[1] + "\",\n"
eloc_json += "\t\t\t\t\"value\": " + str(i[0]) + "\n"
eloc_json += "\t\t\t},"
else:
if not self.metrics.cyclomatic_complexity_density:
eloc_json = eloc_json[:-1]
if self.metrics.cyclomatic_complexity_density:
for i in sorted(set([(j, i) for (i, j) in self.metrics.cyclomatic_complexity_density.items()]), reverse=True):
eloc_json += "{\n\t\t\t\t\"type\": \"cyclomatic-complexity-density\",\n"
eloc_json += "\t\t\t\t\"file_name\": \"" + i[1] + "\",\n"
eloc_json += "\t\t\t\t\"value\": {0:.3f}\n".format(i[0])
eloc_json += "\t\t\t},"
else:
eloc_json = eloc_json[:-1]
print(",\n\t\t\"metrics\": {\n\t\t\t\"violations\": [\n\t\t\t" + eloc_json + "]\n\t\t}", end="")
def output_xml(self):
if not self.metrics.eloc and not self.metrics.cyclomatic_complexity and not self.metrics.cyclomatic_complexity_density:
print("\t<metrics>\n\t\t<message>" + _(METRICS_MISSING_INFO_TEXT) + "</message>\n\t</metrics>")
else:
eloc_xml = ""
if self.metrics.eloc:
for i in sorted(set([(j, i) for (i, j) in self.metrics.eloc.items()]), reverse=True):
eloc_xml += "\t\t\t<estimated-lines-of-code>\n"
eloc_xml += "\t\t\t\t<file-name>" + i[1] + "</file-name>\n"
eloc_xml += "\t\t\t\t<value>" + str(i[0]) + "</value>\n"
eloc_xml += "\t\t\t</estimated-lines-of-code>\n"
if self.metrics.cyclomatic_complexity:
for i in sorted(set([(j, i) for (i, j) in self.metrics.cyclomatic_complexity.items()]), reverse=True):
eloc_xml += "\t\t\t<cyclomatic-complexity>\n"
eloc_xml += "\t\t\t\t<file-name>" + i[1] + "</file-name>\n"
eloc_xml += "\t\t\t\t<value>" + str(i[0]) + "</value>\n"
eloc_xml += "\t\t\t</cyclomatic-complexity>\n"
if self.metrics.cyclomatic_complexity_density:
for i in sorted(set([(j, i) for (i, j) in self.metrics.cyclomatic_complexity_density.items()]), reverse=True):
eloc_xml += "\t\t\t<cyclomatic-complexity-density>\n"
eloc_xml += "\t\t\t\t<file-name>" + i[1] + "</file-name>\n"
eloc_xml += "\t\t\t\t<value>{0:.3f}</value>\n".format(i[0])
eloc_xml += "\t\t\t</cyclomatic-complexity-density>\n"
print("\t<metrics>\n\t\t<violations>\n" + eloc_xml + "\t\t</violations>\n\t</metrics>")

View File

@ -19,21 +19,26 @@
from __future__ import print_function
from __future__ import unicode_literals
import format
from .. import format
class Outputable(object):
def output_html(self):
print(_("HTML output not yet supported in") + " \"" + self.__class__.__name__ + "\".")
raise NotImplementedError(_("HTML output not yet supported in") + " \"" + self.__class__.__name__ + "\".")
def output_json(self):
raise NotImplementedError(_("JSON output not yet supported in") + " \"" + self.__class__.__name__ + "\".")
def output_text(self):
print(_("Text output not yet supported in") + " \"" + self.__class__.__name__ + "\".")
raise NotImplementedError(_("Text output not yet supported in") + " \"" + self.__class__.__name__ + "\".")
def output_xml(self):
print(_("XML output not yet supported in") + " \"" + self.__class__.__name__ + "\".")
raise NotImplementedError(_("XML output not yet supported in") + " \"" + self.__class__.__name__ + "\".")
def output(outputable):
if format.get_selected() == "html" or format.get_selected() == "htmlembedded":
outputable.output_html()
elif format.get_selected() == "json":
outputable.output_json()
elif format.get_selected() == "text":
outputable.output_text()
else:

View File

@ -0,0 +1,143 @@
# coding: utf-8
#
# Copyright © 2012-2015 Ejwa Software. All rights reserved.
#
# This file is part of gitinspector.
#
# gitinspector is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# gitinspector is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with gitinspector. If not, see <http://www.gnu.org/licenses/>.
from __future__ import print_function
from __future__ import unicode_literals
import textwrap
from ..localization import N_
from .. import format, gravatar, terminal
from .. import responsibilities as resp
from .outputable import Outputable
RESPONSIBILITIES_INFO_TEXT = N_("The following responsibilities, by author, were found in the current "
"revision of the repository (comments are excluded from the line count, "
"if possible)")
MOSTLY_RESPONSIBLE_FOR_TEXT = N_("is mostly responsible for")
class ResponsibilitiesOutput(Outputable):
def __init__(self, changes, blame):
self.changes = changes
self.blame = blame
Outputable.__init__(self)
def output_text(self):
print("\n" + textwrap.fill(_(RESPONSIBILITIES_INFO_TEXT) + ":", width=terminal.get_size()[0]))
for i in sorted(set(i[0] for i in self.blame.blames)):
responsibilities = sorted(((i[1], i[0]) for i in resp.Responsibilities.get(self.blame, i)), reverse=True)
if responsibilities:
print("\n" + i, _(MOSTLY_RESPONSIBLE_FOR_TEXT) + ":")
for j, entry in enumerate(responsibilities):
(width, _unused) = terminal.get_size()
width -= 7
print(str(entry[0]).rjust(6), end=" ")
print("...%s" % entry[1][-width+3:] if len(entry[1]) > width else entry[1])
if j >= 9:
break
def output_html(self):
resp_xml = "<div><div class=\"box\" id=\"responsibilities\">"
resp_xml += "<p>" + _(RESPONSIBILITIES_INFO_TEXT) + ".</p>"
for i in sorted(set(i[0] for i in self.blame.blames)):
responsibilities = sorted(((i[1], i[0]) for i in resp.Responsibilities.get(self.blame, i)), reverse=True)
if responsibilities:
resp_xml += "<div>"
if format.get_selected() == "html":
author_email = self.changes.get_latest_email_by_author(i)
resp_xml += "<h3><img src=\"{0}\"/>{1} {2}</h3>".format(gravatar.get_url(author_email, size=32),
i, _(MOSTLY_RESPONSIBLE_FOR_TEXT))
else:
resp_xml += "<h3>{0} {1}</h3>".format(i, _(MOSTLY_RESPONSIBLE_FOR_TEXT))
for j, entry in enumerate(responsibilities):
resp_xml += "<div" + (" class=\"odd\">" if j % 2 == 1 else ">") + entry[1] + \
" (" + str(entry[0]) + " eloc)</div>"
if j >= 9:
break
resp_xml += "</div>"
resp_xml += "</div></div>"
print(resp_xml)
def output_json(self):
message_json = "\t\t\t\"message\": \"" + _(RESPONSIBILITIES_INFO_TEXT) + "\",\n"
resp_json = ""
for i in sorted(set(i[0] for i in self.blame.blames)):
responsibilities = sorted(((i[1], i[0]) for i in resp.Responsibilities.get(self.blame, i)), reverse=True)
if responsibilities:
author_email = self.changes.get_latest_email_by_author(i)
resp_json += "{\n"
resp_json += "\t\t\t\t\"name\": \"" + i + "\",\n"
resp_json += "\t\t\t\t\"email\": \"" + author_email + "\",\n"
resp_json += "\t\t\t\t\"gravatar\": \"" + gravatar.get_url(author_email) + "\",\n"
resp_json += "\t\t\t\t\"files\": [\n\t\t\t\t"
for j, entry in enumerate(responsibilities):
resp_json += "{\n"
resp_json += "\t\t\t\t\t\"name\": \"" + entry[1] + "\",\n"
resp_json += "\t\t\t\t\t\"rows\": " + str(entry[0]) + "\n"
resp_json += "\t\t\t\t},"
if j >= 9:
break
resp_json = resp_json[:-1]
resp_json += "]\n\t\t\t},"
resp_json = resp_json[:-1]
print(",\n\t\t\"responsibilities\": {\n" + message_json + "\t\t\t\"authors\": [\n\t\t\t" + resp_json + "]\n\t\t}", end="")
def output_xml(self):
message_xml = "\t\t<message>" + _(RESPONSIBILITIES_INFO_TEXT) + "</message>\n"
resp_xml = ""
for i in sorted(set(i[0] for i in self.blame.blames)):
responsibilities = sorted(((i[1], i[0]) for i in resp.Responsibilities.get(self.blame, i)), reverse=True)
if responsibilities:
author_email = self.changes.get_latest_email_by_author(i)
resp_xml += "\t\t\t<author>\n"
resp_xml += "\t\t\t\t<name>" + i + "</name>\n"
resp_xml += "\t\t\t\t<email>" + author_email + "</email>\n"
resp_xml += "\t\t\t\t<gravatar>" + gravatar.get_url(author_email) + "</gravatar>\n"
resp_xml += "\t\t\t\t<files>\n"
for j, entry in enumerate(responsibilities):
resp_xml += "\t\t\t\t\t<file>\n"
resp_xml += "\t\t\t\t\t\t<name>" + entry[1] + "</name>\n"
resp_xml += "\t\t\t\t\t\t<rows>" + str(entry[0]) + "</rows>\n"
resp_xml += "\t\t\t\t\t</file>\n"
if j >= 9:
break
resp_xml += "\t\t\t\t</files>\n"
resp_xml += "\t\t\t</author>\n"
print("\t<responsibilities>\n" + message_xml + "\t\t<authors>\n" + resp_xml + "\t\t</authors>\n\t</responsibilities>")

View File

@ -0,0 +1,208 @@
# coding: utf-8
#
# Copyright © 2012-2015 Ejwa Software. All rights reserved.
#
# This file is part of gitinspector.
#
# gitinspector is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# gitinspector is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with gitinspector. If not, see <http://www.gnu.org/licenses/>.
from __future__ import print_function
from __future__ import unicode_literals
import textwrap
from ..localization import N_
from .. import format, gravatar, terminal, timeline
from .outputable import Outputable
TIMELINE_INFO_TEXT = N_("The following history timeline has been gathered from the repository")
MODIFIED_ROWS_TEXT = N_("Modified Rows:")
def __output_row__text__(timeline_data, periods, names):
print("\n" + terminal.__bold__ + terminal.ljust(_("Author"), 20), end=" ")
for period in periods:
print(terminal.rjust(period, 10), end=" ")
print(terminal.__normal__)
for name in names:
if timeline_data.is_author_in_periods(periods, name[0]):
print(terminal.ljust(name[0], 20)[0:20 - terminal.get_excess_column_count(name[0])], end=" ")
for period in periods:
multiplier = timeline_data.get_multiplier(period, 9)
signs = timeline_data.get_author_signs_in_period(name[0], period, multiplier)
signs_str = (signs[1] * "-" + signs[0] * "+")
print (("." if timeline_data.is_author_in_period(period, name[0]) and
len(signs_str) == 0 else signs_str).rjust(10), end=" ")
print("")
print(terminal.__bold__ + terminal.ljust(_(MODIFIED_ROWS_TEXT), 20) + terminal.__normal__, end=" ")
for period in periods:
total_changes = str(timeline_data.get_total_changes_in_period(period)[2])
if hasattr(total_changes, 'decode'):
total_changes = total_changes.decode("utf-8", "replace")
print(terminal.rjust(total_changes, 10), end=" ")
print("")
def __output_row__html__(timeline_data, periods, names):
timeline_xml = "<table class=\"git full\"><thead><tr><th>" + _("Author") + "</th>"
for period in periods:
timeline_xml += "<th>" + str(period) + "</th>"
timeline_xml += "</tr></thead><tbody>"
i = 0
for name in names:
if timeline_data.is_author_in_periods(periods, name[0]):
timeline_xml += "<tr" + (" class=\"odd\">" if i % 2 == 1 else ">")
if format.get_selected() == "html":
timeline_xml += "<td><img src=\"{0}\"/>{1}</td>".format(gravatar.get_url(name[1]), name[0])
else:
timeline_xml += "<td>" + name[0] + "</td>"
for period in periods:
multiplier = timeline_data.get_multiplier(period, 18)
signs = timeline_data.get_author_signs_in_period(name[0], period, multiplier)
signs_str = (signs[1] * "<div class=\"remove\">&nbsp;</div>" + signs[0] * "<div class=\"insert\">&nbsp;</div>")
timeline_xml += "<td>" + ("." if timeline_data.is_author_in_period(period, name[0]) and len(signs_str) == 0 else signs_str)
timeline_xml += "</td>"
timeline_xml += "</tr>"
i = i + 1
timeline_xml += "<tfoot><tr><td><strong>" + _(MODIFIED_ROWS_TEXT) + "</strong></td>"
for period in periods:
total_changes = timeline_data.get_total_changes_in_period(period)
timeline_xml += "<td>" + str(total_changes[2]) + "</td>"
timeline_xml += "</tr></tfoot></tbody></table>"
print(timeline_xml)
class TimelineOutput(Outputable):
def __init__(self, changes, useweeks):
self.changes = changes
self.useweeks = useweeks
Outputable.__init__(self)
def output_text(self):
if self.changes.get_commits():
print("\n" + textwrap.fill(_(TIMELINE_INFO_TEXT) + ":", width=terminal.get_size()[0]))
timeline_data = timeline.TimelineData(self.changes, self.useweeks)
periods = timeline_data.get_periods()
names = timeline_data.get_authors()
(width, _unused) = terminal.get_size()
max_periods_per_row = int((width - 21) / 11)
for i in range(0, len(periods), max_periods_per_row):
__output_row__text__(timeline_data, periods[i:i+max_periods_per_row], names)
def output_html(self):
if self.changes.get_commits():
timeline_data = timeline.TimelineData(self.changes, self.useweeks)
periods = timeline_data.get_periods()
names = timeline_data.get_authors()
max_periods_per_row = 8
timeline_xml = "<div><div id=\"timeline\" class=\"box\">"
timeline_xml += "<p>" + _(TIMELINE_INFO_TEXT) + ".</p>"
print(timeline_xml)
for i in range(0, len(periods), max_periods_per_row):
__output_row__html__(timeline_data, periods[i:i+max_periods_per_row], names)
timeline_xml = "</div></div>"
print(timeline_xml)
def output_json(self):
if self.changes.get_commits():
message_json = "\t\t\t\"message\": \"" + _(TIMELINE_INFO_TEXT) + "\",\n"
timeline_json = ""
periods_json = "\t\t\t\"period_length\": \"{0}\",\n".format("week" if self.useweeks else "month")
periods_json += "\t\t\t\"periods\": [\n\t\t\t"
timeline_data = timeline.TimelineData(self.changes, self.useweeks)
periods = timeline_data.get_periods()
names = timeline_data.get_authors()
for period in periods:
name_json = "\t\t\t\t\"name\": \"" + str(period) + "\",\n"
authors_json = "\t\t\t\t\"authors\": [\n\t\t\t\t"
for name in names:
if timeline_data.is_author_in_period(period, name[0]):
multiplier = timeline_data.get_multiplier(period, 24)
signs = timeline_data.get_author_signs_in_period(name[0], period, multiplier)
signs_str = (signs[1] * "-" + signs[0] * "+")
if len(signs_str) == 0:
signs_str = "."
authors_json += "{\n\t\t\t\t\t\"name\": \"" + name[0] + "\",\n"
authors_json += "\t\t\t\t\t\"email\": \"" + name[1] + "\",\n"
authors_json += "\t\t\t\t\t\"gravatar\": \"" + gravatar.get_url(name[1]) + "\",\n"
authors_json += "\t\t\t\t\t\"work\": \"" + signs_str + "\"\n\t\t\t\t},"
else:
authors_json = authors_json[:-1]
authors_json += "],\n"
modified_rows_json = "\t\t\t\t\"modified_rows\": " + \
str(timeline_data.get_total_changes_in_period(period)[2]) + "\n"
timeline_json += "{\n" + name_json + authors_json + modified_rows_json + "\t\t\t},"
else:
timeline_json = timeline_json[:-1]
print(",\n\t\t\"timeline\": {\n" + message_json + periods_json + timeline_json + "]\n\t\t}", end="")
def output_xml(self):
if self.changes.get_commits():
message_xml = "\t\t<message>" + _(TIMELINE_INFO_TEXT) + "</message>\n"
timeline_xml = ""
periods_xml = "\t\t<periods length=\"{0}\">\n".format("week" if self.useweeks else "month")
timeline_data = timeline.TimelineData(self.changes, self.useweeks)
periods = timeline_data.get_periods()
names = timeline_data.get_authors()
for period in periods:
name_xml = "\t\t\t\t<name>" + str(period) + "</name>\n"
authors_xml = "\t\t\t\t<authors>\n"
for name in names:
if timeline_data.is_author_in_period(period, name[0]):
multiplier = timeline_data.get_multiplier(period, 24)
signs = timeline_data.get_author_signs_in_period(name[0], period, multiplier)
signs_str = (signs[1] * "-" + signs[0] * "+")
if len(signs_str) == 0:
signs_str = "."
authors_xml += "\t\t\t\t\t<author>\n\t\t\t\t\t\t<name>" + name[0] + "</name>\n"
authors_xml += "\t\t\t\t\t\t<email>" + name[1] + "</email>\n"
authors_xml += "\t\t\t\t\t\t<gravatar>" + gravatar.get_url(name[1]) + "</gravatar>\n"
authors_xml += "\t\t\t\t\t\t<work>" + signs_str + "</work>\n\t\t\t\t\t</author>\n"
authors_xml += "\t\t\t\t</authors>\n"
modified_rows_xml = "\t\t\t\t<modified_rows>" + \
str(timeline_data.get_total_changes_in_period(period)[2]) + "</modified_rows>\n"
timeline_xml += "\t\t\t<period>\n" + name_xml + authors_xml + modified_rows_xml + "\t\t\t</period>\n"
print("\t<timeline>\n" + message_xml + periods_xml + timeline_xml + "\t\t</periods>\n\t</timeline>")

View File

@ -1,6 +1,6 @@
# coding: utf-8
#
# Copyright © 2012-2014 Ejwa Software. All rights reserved.
# Copyright © 2012-2015 Ejwa Software. All rights reserved.
#
# This file is part of gitinspector.
#
@ -19,111 +19,19 @@
from __future__ import print_function
from __future__ import unicode_literals
from localization import N_
from outputable import Outputable
import blame
import changes
import format
import gravatar
import terminal
import textwrap
class ResponsibiltyEntry:
class ResponsibiltyEntry(object):
blames = {}
class Responsibilities:
class Responsibilities(object):
@staticmethod
def get(hard, useweeks, author_name):
def get(blame, author_name):
author_blames = {}
for i in blame.get(hard, useweeks, changes.get(hard)).blames.items():
if (author_name == i[0][0]):
for i in blame.blames.items():
if author_name == i[0][0]:
total_rows = i[1].rows - i[1].comments
if total_rows > 0:
author_blames[i[0][1]] = total_rows
return sorted(author_blames.items())
RESPONSIBILITIES_INFO_TEXT = N_("The following repsonsibilties, by author, were found in the current "
"revision of the repository (comments are exluded from the line count, "
"if possible)")
MOSTLY_RESPONSIBLE_FOR_TEXT = N_("is mostly responsible for")
class ResponsibilitiesOutput(Outputable):
def __init__(self, hard, useweeks):
self.hard = hard
self.useweeks = useweeks
Outputable.__init__(self)
self.changes = changes.get(hard)
def output_text(self):
print("\n" + textwrap.fill(_(RESPONSIBILITIES_INFO_TEXT) + ":", width=terminal.get_size()[0]))
for i in sorted(set(i[0] for i in blame.get(self.hard, self.useweeks, self.changes).blames)):
responsibilities = sorted(((i[1], i[0]) for i in Responsibilities.get(self.hard, self.useweeks, i)), reverse=True)
if responsibilities:
print("\n" + i, _(MOSTLY_RESPONSIBLE_FOR_TEXT) + ":")
for j, entry in enumerate(responsibilities):
(width, _unused) = terminal.get_size()
width -= 7
print(str(entry[0]).rjust(6), end=" ")
print("...%s" % entry[1][-width+3:] if len(entry[1]) > width else entry[1])
if j >= 9:
break
def output_html(self):
resp_xml = "<div><div class=\"box\" id=\"responsibilities\">"
resp_xml += "<p>" + _(RESPONSIBILITIES_INFO_TEXT) + ".</p>"
for i in sorted(set(i[0] for i in blame.get(self.hard, self.useweeks, self.changes).blames)):
responsibilities = sorted(((i[1], i[0]) for i in Responsibilities.get(self.hard, self.useweeks, i)), reverse=True)
if responsibilities:
resp_xml += "<div>"
if format.get_selected() == "html":
author_email = self.changes.get_latest_email_by_author(i)
resp_xml += "<h3><img src=\"{0}\"/>{1} {2}</h3>".format(gravatar.get_url(author_email, size=32),
i, _(MOSTLY_RESPONSIBLE_FOR_TEXT))
else:
resp_xml += "<h3>{0} {1}</h3>".format(i, _(MOSTLY_RESPONSIBLE_FOR_TEXT))
for j, entry in enumerate(responsibilities):
resp_xml += "<div" + (" class=\"odd\">" if j % 2 == 1 else ">") + entry[1] + \
" (" + str(entry[0]) + " eloc)</div>"
if j >= 9:
break
resp_xml += "</div>"
resp_xml += "</div></div>"
print(resp_xml)
def output_xml(self):
message_xml = "\t\t<message>" + _(RESPONSIBILITIES_INFO_TEXT) + "</message>\n"
resp_xml = ""
for i in sorted(set(i[0] for i in blame.get(self.hard, self.useweeks, self.changes).blames)):
responsibilities = sorted(((i[1], i[0]) for i in Responsibilities.get(self.hard, self.useweeks, i)), reverse=True)
if responsibilities:
author_email = self.changes.get_latest_email_by_author(i)
resp_xml += "\t\t\t<author>\n"
resp_xml += "\t\t\t\t<name>" + i + "</name>\n"
resp_xml += "\t\t\t\t<gravatar>" + gravatar.get_url(author_email) + "</gravatar>\n"
resp_xml += "\t\t\t\t<files>\n"
for j, entry in enumerate(responsibilities):
resp_xml += "\t\t\t\t\t<file>\n"
resp_xml += "\t\t\t\t\t\t<name>" + entry[1] + "</name>\n"
resp_xml += "\t\t\t\t\t\t<rows>" + str(entry[0]) + "</rows>\n"
resp_xml += "\t\t\t\t\t</file>\n"
if j >= 9:
break
resp_xml += "\t\t\t\t</files>\n"
resp_xml += "\t\t\t</author>\n"
print("\t<responsibilities>\n" + message_xml + "\t\t<authors>\n" + resp_xml + "\t\t</authors>\n\t</responsibilities>")

View File

@ -1,6 +1,6 @@
# coding: utf-8
#
# Copyright © 2012-2014 Ejwa Software. All rights reserved.
# Copyright © 2012-2015 Ejwa Software. All rights reserved.
#
# This file is part of gitinspector.
#
@ -24,8 +24,8 @@ import platform
import sys
import unicodedata
__bold__ = "\033[1m"
__normal__ = "\033[0;0m"
__bold__ = "\033[1m"
__normal__ = "\033[0;0m"
DEFAULT_TERMINAL_SIZE = (80, 25)
@ -77,7 +77,7 @@ def __get_size_linux__():
return int(size[1]), int(size[0])
def clear_row():
print("\b" * 200, end="")
print("\r", end="")
def skip_escapes(skip):
if skip:
@ -134,9 +134,9 @@ def get_excess_column_count(string):
width_mapping = {'F': 2, 'H': 1, 'W': 2, 'Na': 1, 'N': 1, 'A': 1}
result = 0
for c in string:
w = unicodedata.east_asian_width(c)
result += width_mapping[w]
for i in string:
width = unicodedata.east_asian_width(i)
result += width_mapping[width]
return result - len(string)
@ -145,3 +145,14 @@ def ljust(string, pad):
def rjust(string, pad):
return string.rjust(pad - get_excess_column_count(string))
def output_progress(text, pos, length):
if sys.stdout.isatty():
(width, _unused) = get_size()
progress_text = text.format(100 * pos / length)
if len(progress_text) > width:
progress_text = "...%s" % progress_text[-width+3:]
print("\r{0}\r{1}".format(" " * width, progress_text), end="")
sys.stdout.flush()

View File

@ -1,6 +1,6 @@
# coding: utf-8
#
# Copyright © 2012-2014 Ejwa Software. All rights reserved.
# Copyright © 2012-2015 Ejwa Software. All rights reserved.
#
# This file is part of gitinspector.
#
@ -17,17 +17,10 @@
# You should have received a copy of the GNU General Public License
# along with gitinspector. If not, see <http://www.gnu.org/licenses/>.
from __future__ import print_function
from __future__ import unicode_literals
from localization import N_
from outputable import Outputable
import datetime
import format
import gravatar
import terminal
import textwrap
class TimelineData:
class TimelineData(object):
def __init__(self, changes, useweeks):
authordateinfo_list = sorted(changes.get_authordateinfo_list().items())
self.changes = changes
@ -105,146 +98,3 @@ class TimelineData:
if self.is_author_in_period(period, author):
return True
return False
TIMELINE_INFO_TEXT = N_("The following history timeline has been gathered from the repository")
MODIFIED_ROWS_TEXT = N_("Modified Rows:")
def __output_row__text__(timeline_data, periods, names):
print("\n" + terminal.__bold__ + terminal.ljust(_("Author"), 20), end=" ")
for period in periods:
print(terminal.rjust(period, 10), end=" ")
print(terminal.__normal__)
for name in names:
if timeline_data.is_author_in_periods(periods, name[0]):
print(terminal.ljust(name[0], 20)[0:20 - terminal.get_excess_column_count(name[0])], end=" ")
for period in periods:
multiplier = timeline_data.get_multiplier(period, 9)
signs = timeline_data.get_author_signs_in_period(name[0], period, multiplier)
signs_str = (signs[1] * "-" + signs[0] * "+")
print (("." if timeline_data.is_author_in_period(period, name[0]) and
len(signs_str) == 0 else signs_str).rjust(10), end=" ")
print("")
print(terminal.__bold__ + terminal.ljust(_(MODIFIED_ROWS_TEXT), 20) + terminal.__normal__, end=" ")
for period in periods:
total_changes = str(timeline_data.get_total_changes_in_period(period)[2])
if hasattr(total_changes, 'decode'):
total_changes = total_changes.decode("utf-8", "replace")
print(terminal.rjust(total_changes, 10), end=" ")
print("")
def __output_row__html__(timeline_data, periods, names):
timeline_xml = "<table class=\"git full\"><thead><tr><th>" + _("Author") + "</th>"
for period in periods:
timeline_xml += "<th>" + str(period) + "</th>"
timeline_xml += "</tr></thead><tbody>"
i = 0
for name in names:
if timeline_data.is_author_in_periods(periods, name[0]):
timeline_xml += "<tr" + (" class=\"odd\">" if i % 2 == 1 else ">")
if format.get_selected() == "html":
timeline_xml += "<td><img src=\"{0}\"/>{1}</td>".format(gravatar.get_url(name[1]), name[0])
else:
timeline_xml += "<td>" + name[0] + "</td>"
for period in periods:
multiplier = timeline_data.get_multiplier(period, 18)
signs = timeline_data.get_author_signs_in_period(name[0], period, multiplier)
signs_str = (signs[1] * "<div class=\"remove\">&nbsp;</div>" + signs[0] * "<div class=\"insert\">&nbsp;</div>")
timeline_xml += "<td>" + ("." if timeline_data.is_author_in_period(period, name[0]) and len(signs_str) == 0 else signs_str)
timeline_xml += "</td>"
timeline_xml += "</tr>"
i = i + 1
timeline_xml += "<tfoot><tr><td><strong>" + _(MODIFIED_ROWS_TEXT) + "</strong></td>"
for period in periods:
total_changes = timeline_data.get_total_changes_in_period(period)
timeline_xml += "<td>" + str(total_changes[2]) + "</td>"
timeline_xml += "</tr></tfoot></tbody></table>"
print(timeline_xml)
class Timeline(Outputable):
def __init__(self, changes, useweeks):
self.changes = changes
self.useweeks = useweeks
Outputable.__init__(self)
def output_text(self):
if self.changes.get_commits():
print("\n" + textwrap.fill(_(TIMELINE_INFO_TEXT) + ":", width=terminal.get_size()[0]))
timeline_data = TimelineData(self.changes, self.useweeks)
periods = timeline_data.get_periods()
names = timeline_data.get_authors()
(width, _unused) = terminal.get_size()
max_periods_per_row = int((width - 21) / 11)
for i in range(0, len(periods), max_periods_per_row):
__output_row__text__(timeline_data, periods[i:i+max_periods_per_row], names)
def output_html(self):
if self.changes.get_commits():
timeline_data = TimelineData(self.changes, self.useweeks)
periods = timeline_data.get_periods()
names = timeline_data.get_authors()
max_periods_per_row = 8
timeline_xml = "<div><div id=\"timeline\" class=\"box\">"
timeline_xml += "<p>" + _(TIMELINE_INFO_TEXT) + ".</p>"
print(timeline_xml)
for i in range(0, len(periods), max_periods_per_row):
__output_row__html__(timeline_data, periods[i:i+max_periods_per_row], names)
timeline_xml = "</div></div>"
print(timeline_xml)
def output_xml(self):
if self.changes.get_commits():
message_xml = "\t\t<message>" + _(TIMELINE_INFO_TEXT) + "</message>\n"
timeline_xml = ""
periods_xml = "\t\t<periods length=\"{0}\">\n".format("week" if self.useweeks else "month")
timeline_data = TimelineData(self.changes, self.useweeks)
periods = timeline_data.get_periods()
names = timeline_data.get_authors()
for period in periods:
name_xml = "\t\t\t\t<name>" + str(period) + "</name>\n"
authors_xml = "\t\t\t\t<authors>\n"
for name in names:
if timeline_data.is_author_in_period(period, name[0]):
multiplier = timeline_data.get_multiplier(period, 24)
signs = timeline_data.get_author_signs_in_period(name[0], period, multiplier)
signs_str = (signs[1] * "-" + signs[0] * "+")
if len(signs_str) == 0:
signs_str = "."
authors_xml += "\t\t\t\t\t<author>\n\t\t\t\t\t\t<name>" + name[0] + "</name>\n"
authors_xml += "\t\t\t\t\t\t<gravatar>" + gravatar.get_url(name[1]) + "</gravatar>\n"
authors_xml += "\t\t\t\t\t\t<work>" + signs_str + "</work>\n\t\t\t\t\t</author>\n"
authors_xml += "\t\t\t\t</authors>\n"
modified_rows_xml = "\t\t\t\t<modified_rows>" + \
str(timeline_data.get_total_changes_in_period(period)[2]) + "</modified_rows>\n"
timeline_xml += "\t\t\t<period>\n" + name_xml + authors_xml + modified_rows_xml + "\t\t\t</period>\n"
print("\t<timeline>\n" + message_xml + periods_xml + timeline_xml + "\t\t</periods>\n\t</timeline>")

View File

@ -24,9 +24,9 @@
msgid ""
msgstr ""
"Project-Id-Version: gitinspector 0.4.1\n"
"Project-Id-Version: gitinspector 0.5.0dev\n"
"Report-Msgid-Bugs-To: gitinspector@ejwa.se\n"
"POT-Creation-Date: 2014-11-29 05:20+0100\n"
"POT-Creation-Date: 2015-10-02 03:35+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Adam Waldenberg <adam.waldenberg@ejwa.se>\n"
"MIME-Version: 1.0\n"
@ -153,7 +153,7 @@ msgstr ""
msgid "The following history timeline has been gathered from the repository"
msgstr ""
msgid "The following repsonsibilties, by author, were found in the current revision of the repository (comments are exluded from the line count, if possible)"
msgid "The following responsibilities, by author, were found in the current revision of the repository (comments are excluded from the line count, if possible)"
msgstr ""
msgid "The given option argument is not a valid boolean."
@ -183,6 +183,8 @@ msgid ""
" include when computing statistics. The\n"
" default extensions used are:\n"
" {1}\n"
" Specifying * includes files with no\n"
" extension, while ** includes all files\n"
" -F, --format=FORMAT define in which format output should be\n"
" generated; the default format is 'text' and\n"
" the available formats are:\n"
@ -210,7 +212,8 @@ msgid ""
" -w, --weeks[=BOOL] show all statistical information in weeks\n"
" instead of in months\n"
" -x, --exclude=PATTERN an exclusion pattern describing the file\n"
" paths, revisions, author names or author\n"
" paths, revisions, revisions with certain\n"
" commit messages, author names or author\n"
" emails that should be excluded from the\n"
" statistics; can be specified multiple times\n"
" -h, --help display this help and exit\n"

View File

@ -15,33 +15,34 @@
# You should have received a copy of the GNU General Public License
# along with gitinspector. If not, see <http://www.gnu.org/licenses/>.
#
# Philipp Nowak <devnull@nowak-at.net>, 2014
# Philipp Nowak <devnull@nowak-at.net>, 2014-2015
msgid ""
msgstr ""
"Project-Id-Version: gitinspector 0.4.1\n"
"Project-Id-Version: gitinspector 0.5.0dev\n"
"Report-Msgid-Bugs-To: gitinspector@ejwa.se\n"
"POT-Creation-Date: 2014-11-29 05:20+0100\n"
"PO-Revision-Date: 2014-12-08 11:17+0100\n"
"POT-Creation-Date: 2015-10-02 03:35+0200\n"
"PO-Revision-Date: 2015-10-21 00:25+0200\n"
"Last-Translator: Philipp Nowak <devnull@nowak-at.net>\n"
"Language-Team: Deutsch <>\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 1.6.9\n"
"X-Generator: Poedit 1.8.5\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#, python-format
msgid "% in comments"
msgstr "% in Kommentaren"
# Poedit complains on validate when using a non-valid format specifier after the percent sign, even if separated by a space. It claims that argument number must be equal in both format and message, so can't use the word. Correct translation would be "% gesamt"
#, python-format
msgid "% of changes"
msgstr "% d. Änderungen"
msgid "(extensions used during statistical analysis are marked)"
msgstr "(Erweiterungen, die in die Statistik einbezogen wurden, sind markiert)"
msgstr "(Einbezogene markiert)"
msgid "Age"
msgstr "Alter"
@ -49,25 +50,20 @@ msgstr "Alter"
msgid "Author"
msgstr "Autor"
msgid ""
"Below are the number of rows from each author that have survived and are "
"still intact in the current revision"
msgstr ""
"In der folgenden Tabelle sind die in der aktuellen Codebase erhaltenen "
"Zeilen zusammengefasst, nach Autor geordnet"
# spaces because this needs to override the progress message displayed before
msgid "Below are the number of rows from each author that have survived and are still intact in the current revision"
msgstr "Noch intakte Codezeilen nach Autoren "
#, python-brace-format
msgid "Checking how many rows belong to each author (Progress): {0:.0f}%"
msgstr ""
"Überprüfe, wie viele Spalten zu jedem Autor gehören... Fortschritt: {0:.0f}%"
msgstr "Berechne intakte Zeilen pro Autor...{0:.0f}%"
msgid "Commits"
msgstr "Commits"
msgid ""
"Copyright © 2012-2015 Ejwa Software. All rights reserved.\n"
"License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl."
"html>.\n"
"License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
"This is free software: you are free to change and redistribute it.\n"
"There is NO WARRANTY, to the extent permitted by law.\n"
"\n"
@ -75,15 +71,16 @@ msgid ""
msgstr ""
"Copyright © 2012-2015 Ejwa Software. Alle Rechte vorbehalten.\n"
"Lizensiert unter der GNU GPL Version 3 oder neuer\n"
"Dies ist freie Software, die Sie unter bestimmten Bedingungen weitergeben "
"Dies ist freie Software, die Sie unter bestimmten Bedingungen weitergeben\n"
"dürfen.\n"
"\n"
"Für dieses Programm besteht KEINERLEI GARANTIE.\n"
"Siehe <http://gnu.org/licenses/gpl.html> für Details.\n"
"\n"
"Geschrieben von Adam Waldenberg."
msgid "Deletions"
msgstr "Löschungen"
msgstr "entfernt"
#, python-format
msgid "Error processing git repository at \"%s\"."
@ -99,7 +96,7 @@ msgid "Hide rows with minor work"
msgstr "Verstecke irrelevante Autoren"
msgid "Insertions"
msgstr "Einfügungen"
msgstr "hinzugefügt"
msgid "Minor Authors"
msgstr "Sonstige"
@ -108,7 +105,7 @@ msgid "Modified Rows:"
msgstr "Bearbeitete Zeilen:"
msgid "No commited files with the specified extensions were found"
msgstr "Keine eingecheckten Dateien mit den angegebenen Erweiterungen gefunden"
msgstr "Keine eingecheckten Dateien mit den angegebenen Endungen gefunden"
msgid "No metrics violations were found in the repository"
msgstr "Im Repo wurde keine Verletzung der Metriken gefunden"
@ -136,80 +133,45 @@ msgstr "Statistiken für das Repository '{0}' berechnet am {1}."
msgid "Text output not yet supported in"
msgstr "Textausgabe ist noch nicht unterstützt in"
msgid ""
"The authors with the following emails were excluded from the statistics due "
"to the specified exclusion patterns"
msgstr ""
"Die Autoren mit den folgenden E-Mail-Adressen wurden von der Statistik "
"ausgeschlossen, da sie zu den Ausschlussmustern passten"
msgid "The authors with the following emails were excluded from the statistics due to the specified exclusion patterns"
msgstr "Die Autoren mit den folgenden E-Mail-Adressen wurden von der Statistik ausgeschlossen, da sie zu den Ausschlussmustern passten"
msgid "The extensions below were found in the repository history"
msgstr "Folgende Dateierweiterungen wurden im Repo gefunden"
msgstr "Gefundene Dateiendungen"
msgid ""
"The following authors were excluded from the statistics due to the specified "
"exclusion patterns"
msgstr ""
"Die folgenden Autoren wurden von der Statistik ausgeschlossen, da sie zu den "
"Ausschlussmustern passten"
msgid "The following authors were excluded from the statistics due to the specified exclusion patterns"
msgstr "Die folgenden Autoren wurden von der Statistik ausgeschlossen, da sie zu den Ausschlussmustern passten"
msgid ""
"The following commit revisions were excluded from the statistics due to the "
"specified exclusion patterns"
msgstr ""
"Die folgenden Änderungen wurden von der Statistik ausgeschlossen, da sie zu "
"den Ausschlussmustern passten"
msgid "The following commit revisions were excluded from the statistics due to the specified exclusion patterns"
msgstr "Die folgenden Änderungen wurden von der Statistik ausgeschlossen, da sie zu den Ausschlussmustern passten"
msgid "The following files are suspiciously big (in order of severity)"
msgstr "Die folgenden Dateien sind verdächtig groß (Geordnet nach Schweregrad)"
msgid ""
"The following files have an elevated cyclomatic complexity (in order of "
"severity)"
msgstr ""
"Die folgenden Dateien haben eine sehr hohe McCabe-Metrik (Geordnet nach "
"Schweregrad)"
msgid "The following files have an elevated cyclomatic complexity (in order of severity)"
msgstr "Folgende Dateien haben eine hohe zyklomatische Komplexität (nach Schweregrad)"
msgid ""
"The following files have an elevated cyclomatic complexity density (in order "
"of severity)"
msgstr ""
"Die folgenden Dateien haben eine sehr hohe McCabe-Metrik-Dichte (Geordnet "
"nach Schweregrad)"
msgid "The following files have an elevated cyclomatic complexity density (in order of severity)"
msgstr "Die folgenden Dateien haben eine sehr hohe McCabe-Metrik-Dichte (Geordnet nach Schweregrad)"
msgid ""
"The following files were excluded from the statistics due to the specified "
"exclusion patterns"
msgstr ""
"Die folgenden Dateien wurden von der Statistik ausgeschlossen, da sie zu den "
"Ausschlussmustern passten"
msgid "The following files were excluded from the statistics due to the specified exclusion patterns"
msgstr "Die folgenden Dateien wurden von der Statistik ausgeschlossen, da sie zu den Ausschlussmustern passten"
msgid ""
"The following historical commit information, by author, was found in the "
"repository"
msgstr "In der folgenden Tabelle sind Autoren und Änderungen zusammengefasst"
msgid "The following historical commit information, by author, was found in the repository"
msgstr "Commits nach Autoren"
msgid "The following history timeline has been gathered from the repository"
msgstr ""
"Hier ist eine Zeitleiste, die die Commitaktivität pro Autor zusammenfasst"
msgstr "Commitaktivität nach Autoren"
msgid ""
"The following repsonsibilties, by author, were found in the current revision "
"of the repository (comments are exluded from the line count, if possible)"
msgstr ""
"In der folgenden Tabelle sind die Verantwortlichkeiten pro Autor aufgelistet "
"(Kommentare wurden nach Möglichkeit ignoriert)"
msgid "The following responsibilities, by author, were found in the current revision of the repository (comments are excluded from the line count, if possible)"
msgstr "Verantwortlichkeiten nach Autor (Kommentare nach Möglichkeit ignoriert)"
msgid "The given option argument is not a valid boolean."
msgstr "Das eingegebene Optionsargument ist kein valider Bool'scher Wert"
#, python-brace-format
msgid ""
"The output has been generated by {0} {1}. The statistical analysis tool for "
"git repositories."
msgstr ""
"Die folgende Analyse wurde erstellt mit {0} {1}, dem statistischen "
"Analysetool for Git-Repositories."
msgid "The output has been generated by {0} {1}. The statistical analysis tool for git repositories."
msgstr "Die folgende Analyse wurde erstellt mit {0} {1}, dem statistischen Analysetool for Git-Repositories."
#, python-brace-format
msgid "Try `{0} --help' for more information."
@ -227,53 +189,43 @@ msgid ""
"\n"
"Mandatory arguments to long options are mandatory for short options too.\n"
"Boolean arguments can only be given to long options.\n"
" -f, --file-types=EXTENSIONS a comma separated list of file extensions "
"to\n"
" -f, --file-types=EXTENSIONS a comma separated list of file extensions to\n"
" include when computing statistics. The\n"
" default extensions used are:\n"
" {1}\n"
" Specifying * includes files with no\n"
" extension, while ** includes all files\n"
" -F, --format=FORMAT define in which format output should be\n"
" generated; the default format is 'text' "
"and\n"
" generated; the default format is 'text' and\n"
" the available formats are:\n"
" {2}\n"
" --grading[=BOOL] show statistics and information in a way "
"that\n"
" --grading[=BOOL] show statistics and information in a way that\n"
" is formatted for grading of student\n"
" projects; this is the same as supplying "
"the\n"
" projects; this is the same as supplying the\n"
" options -HlmrTw\n"
" -H, --hard[=BOOL] track rows and look for duplicates harder;\n"
" this can be quite slow with big "
"repositories\n"
" -l, --list-file-types[=BOOL] list all the file extensions available in "
"the\n"
" this can be quite slow with big repositories\n"
" -l, --list-file-types[=BOOL] list all the file extensions available in the\n"
" current branch of the repository\n"
" -L, --localize-output[=BOOL] localize the generated output to the "
"selected\n"
" -L, --localize-output[=BOOL] localize the generated output to the selected\n"
" system language if a translation is\n"
" available\n"
" -m --metrics[=BOOL] include checks for certain metrics during "
"the\n"
" -m --metrics[=BOOL] include checks for certain metrics during the\n"
" analysis of commits\n"
" -r --responsibilities[=BOOL] show which files the different authors "
"seem\n"
" -r --responsibilities[=BOOL] show which files the different authors seem\n"
" most responsible for\n"
" --since=DATE only show statistics for commits more "
"recent\n"
" --since=DATE only show statistics for commits more recent\n"
" than a specific date\n"
" -T, --timeline[=BOOL] show commit timeline, including author "
"names\n"
" --until=DATE only show statistics for commits older than "
"a\n"
" -T, --timeline[=BOOL] show commit timeline, including author names\n"
" --until=DATE only show statistics for commits older than a\n"
" specific date\n"
" -w, --weeks[=BOOL] show all statistical information in weeks\n"
" instead of in months\n"
" -x, --exclude=PATTERN an exclusion pattern describing the file\n"
" paths, revisions, author names or author\n"
" paths, revisions, revisions with certain\n"
" commit messages, author names or author\n"
" emails that should be excluded from the\n"
" statistics; can be specified multiple "
"times\n"
" statistics; can be specified multiple times\n"
" -h, --help display this help and exit\n"
" --version output version information and exit\n"
"\n"
@ -284,101 +236,79 @@ msgid ""
"gitinspector requires that the git executable is available in your PATH.\n"
"Report gitinspector bugs to gitinspector@ejwa.se."
msgstr ""
"Verwendung: {0} [OPTION]... [REPO]\n"
"Zeigt Informationen und Statistiken über das Git-Repo in REPO "
"(Verzeichnis).\n"
"Wenn kein Repo angegeben wurde, wird das aktuelle Verzeichnis verwendet.\n"
"Werden mehrere Repos angegeben, wird das letzte verwendet.\n"
"Verwendung: {0} [OPTION]... [REPOSITORY]\n"
"Zeigt Informationen über das Git-Repository in REPOSITORY. \n"
"Wenn kein Repository angegeben wurde, wird das aktuelle\n"
"Verzeichnis verwendet. Wenn mehrere angegeben sind,\n"
"wird das letzte verwendet.\n"
"\n"
"Erforderliche Argumente für lange Schreibweisen sind auch für "
"Kurzschreibweisen erforderlich.\n"
"Bool'sche Werte können nur langen Schreibweisen übergeben werden.\n"
"\n"
" -f, --file-types=ERWEITERUNGEN Eine kommagetrennte Liste von Datei-\n"
" eweiterungen, die in die Statistik mit\n"
" einbezogen werden sollen.\n"
" Standardwert:\n"
"Erforderliche Argumente für Langschreibweisen der Optionensind auch\n"
"für Kurzschreibweisen erforderlich. Bool'sche Werte (true/false) können\n"
"nur mit Langschreibweisen verwendet werden.\n"
" -f, --file-types=ENDUNGEN Eine kommagetrennte Liste der\n"
" Dateiendungen die in die Analyse\n"
" einbezogen werden sollen. Standard:\n"
" {1}\n"
" -F, --format=FORMAT Definiert das Format der Ausgabe.\n"
" Das Standardformat ist 'text'.\n"
" Verfügbare Formate:\n"
" * bezieht Dateien ohne Endung ein,\n"
" ** bezieht alle Dateien ein.\n"
" -F, --format=FORMAT Definiert das Ausgabeformat der\n"
" Analyse. Der Standard ist 'text', verfügbar:\n"
" {2}\n"
" --grading[=BOOL] Zeige Statistiken so, dass sie\n"
" für die Benotung von Schülern verwendet\n"
" werden können\n"
" Dieses Argument ist ein Alias für -"
"HlmrTw\n"
" -H, --hard[=BOOL] Verfolge Zeilen und suche genauer nach\n"
" Duplikaten; dies kann bei großen Repos "
"lange\n"
" dauern.\n"
" -l, --list-file-types[=BOOL] Zeige alle verfügbaren Dateierweiterungen "
"im\n"
" aktuellen Branch des Repos\n"
" -L, --localize-output[=BOOL] Zeige die Ausgabe in der gewählten "
"Sprache,\n"
" wenn eine Übersetzung vorhanden ist\n"
" -m --metrics[=BOOL] Überprüfe spezielle Metriken während der\n"
" Analyse von Commits\n"
" -r --responsibilities[=BOOL] Zeige, für welche Dateien welcher Autor\n"
" am meisten verantwortlich zu sein "
"scheint\n"
" --since=DATUM Zeige Statistiken nur für Commits, die "
"älter\n"
" als DATUM sind\n"
" -T, --timeline[=BOOL] Zeige die Commit-Zeitleiste (Mit "
"Autornamen)\n"
" --until=DATUM Zeige Statistiken nur für Commits, die "
"jünger\n"
" als DATUM sind\n"
" -w, --weeks[=BOOL] Zeige alle Statistiken in Wochen statt "
"Monaten\n"
" -x, --exclude=MUSTER Ein Ausschlussmuster für\n"
" Pfade, Commithashes, Namen "
"oder E-Mail-Adressen, die von der "
"Statistik\n"
" ausgeschlossen werden sollen.\n"
" Kann mehrmals angegeben werden.\n"
" -h, --help Zeigt diese Hilfe und beendet das Programm\n"
" --version Zeigt die Version und beendet das Programm\n"
" --grading[=BOOL] Formatiert Statistiken so, dass\n"
" sie zur Bewertung von Schülern verwendet\n"
" werden können. Alias für -HlmrTw\n"
" -H, --hard[=BOOL] Verfolge Zeilen und suche genauer\n"
" nach Duplikaten; Kann bei großen\n"
" Repositories sehr lange dauern.\n"
" -l, --list-file-types[=BOOL] Zeige alle Datei-Endungen im aktuellen\n"
" Git Branch\n"
" -L, --localize-output[=BOOL] Übersetze Ausgabe in die Systemsprache,\n"
" wenn eine Übersetzung verfügbar ist.\n"
" -m --metrics[=BOOL] Beziehe spezielle Metriken in die Analyse von\n"
" Commits ein.\n"
" -r --responsibilities[=BOOL] Zeige, für welche Dateien die Autoren am\n"
" meisten verantwortlich zu sein scheinen.\n"
" -T, --timeline[=BOOL] Berechne Commit-Zeitstrahl (nach Autoren)\n"
" --since=DATE Analysiere nur ab einem bestimmten Datum\n"
" --until=DATE Analysiere nur vor einem bestimmten Datum\n"
" -w, --weeks[=BOOL] Stelle Statisiken in Wochen statt Monaten dar\n"
" -x, --exclude=PATTERN Ausschlussmuster für Dateipfade,\n"
" Commithashes, Commitnachrichten,\n"
" Autorennamen oder -E-Mails, die von\n"
" der Analyse ausgeschlossen werden\n"
" sollen; Kann mehrmals angegeben werden\n"
" -h, --help Zeige diese Nachricht und beende\n"
" --version Zeige Version und beende\n"
"\n"
"gitinspector wird Statistiken so filtern, dass diese nur Commits "
"beinhalten, \n"
"die die angegebenen Dateierweiterungen beeinflussen.\n"
" Siehe -f oder --file-types für mehr Informationen.\n"
"gitinspector zeigt nur Statistiken zu Commits, die Dateien\n"
"mit bestimmten Endungen bearbeiten, hinzufügen oder\n"
"löschen. Siehe -f oder --file-types für mehr informationen.\n"
"\n"
"gitinspector benötigt Git im PATH.\n"
"gitinspector benötigt git in PATH.\n"
"Melde Bugs an gitinspector@ejwa.se."
msgid ""
"WARNING: The terminal encoding is not correctly configured. gitinspector "
"might malfunction. The encoding can be configured with the environment "
"variable 'PYTHONIOENCODING'."
msgstr ""
"WARNUNG: Das Terminal ist nicht richtig konfiguriert. Gitinspector könnte "
"Probleme haben. Die Kodierung kann mit der Umgebungsvariable "
"`PYTHONIOENCODING` konfiguriert werden."
msgid "WARNING: The terminal encoding is not correctly configured. gitinspector might malfunction. The encoding can be configured with the environment variable 'PYTHONIOENCODING'."
msgstr "WARNUNG: Das Terminal ist nicht richtig konfiguriert. Gitinspector könnte Probleme haben. Die Kodierung kann mit der Umgebungsvariable `PYTHONIOENCODING` konfiguriert werden."
msgid "XML output not yet supported in"
msgstr "XML-Ausgabe ist noch nicht unterstützt in"
#, python-brace-format
msgid ""
"gitinspector requires at least Python 2.6 to run (version {0} was found)."
msgid "gitinspector requires at least Python 2.6 to run (version {0} was found)."
msgstr "Gitinspector benötigt zumindest Python 2.6 (Du verwendest {0})"
msgid "invalid regular expression specified"
msgstr "Invalider regulärer Ausdruck angegeben"
msgid "is mostly responsible for"
msgstr "ist am meisten verantwortlich für die Dateien:"
msgstr "ist am meisten verantwortlich für"
msgid "specified output format not supported."
msgstr "Das angegebene Ausgabeformat wird nicht unterstützt."
#, python-brace-format
msgid "{0} ({1:.3f} in cyclomatic complexity density)"
msgstr "{0} (McCabe-Metrik-Dichte: {1:.3f})"
msgstr "{0} (zyklomatische Komplexitätsdichte: {1:.3f})"
#, python-brace-format
msgid "{0} ({1} estimated lines of code)"
@ -386,4 +316,4 @@ msgstr "{0} ({1} geschätzte Codezeilen)"
#, python-brace-format
msgid "{0} ({1} in cyclomatic complexity)"
msgstr "{0} (McCabe-Metrik: {1})"
msgstr "{0} (zyklomatische Komplexität: {1})"

Binary file not shown.

View File

@ -0,0 +1,413 @@
# Copyright © 2012-2015 Ejwa Software. All rights reserved.
#
# This file is part of gitinspector.
#
# gitinspector is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# gitinspector is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with gitinspector. If not, see <http://www.gnu.org/licenses/>.
#
# Agustín Cañas <agustincanas@gmail.com>, 2015
msgid ""
msgstr ""
"Project-Id-Version: gitinspector 0.5.0dev\n"
"Report-Msgid-Bugs-To: gitinspector@ejwa.se\n"
"POT-Creation-Date: 2015-10-02 03:35+0200\n"
"PO-Revision-Date: 2015-10-02 22:17+0100\n"
"Last-Translator: Agustín Cañas <agustincanas@gmail.com>\n"
"Language-Team: Spanish <>\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#, python-format
msgid "% in comments"
msgstr "% en comentarios"
#, python-format
msgid "% of changes"
msgstr "% de cambios"
msgid "(extensions used during statistical analysis are marked)"
msgstr "(se marcan las extensiones usadas durante el análisis)"
msgid "Age"
msgstr "Edad"
msgid "Author"
msgstr "Autor"
msgid ""
"Below are the number of rows from each author that have survived and are "
"still intact in the current revision"
msgstr ""
"El bloque siguiente muestra el número de líneas de cada autor que todavía "
"permanecen intactas en la versión actual"
#, python-brace-format
msgid "Checking how many rows belong to each author (Progress): {0:.0f}%"
msgstr ""
"Comprobando cuantas líneas pertenecen a cada autor (Progreso): {0:.0f}%"
msgid "Commits"
msgstr "Commits"
msgid ""
"Copyright © 2012-2015 Ejwa Software. All rights reserved.\n"
"License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl."
"html>.\n"
"This is free software: you are free to change and redistribute it.\n"
"There is NO WARRANTY, to the extent permitted by law.\n"
"\n"
"Written by Adam Waldenberg."
msgstr ""
"Copyright © 2012-2015 Ejwa Software. Todos los derechos reservados.\n"
"Este programa es software libre. Puede redistribuirlo y/o modificarlo bajo\n"
"los términos de la Licencia Pública General de GNU tal como está publicada\n"
"por la Free Software Foundation, bien de la versión 3 de dicha Licencia o\n"
"bien (según su elección) de cualquier versión posterior.\n"
"Esta aplicación es software libre: usted es libre de modificarlo y \n"
"redistribuirlo.\n"
"Este programa se distribuye con la esperanza de que sea útil, pero SIN\n"
"NINGUNA GARANTÍA, incluso sin la garantía MERCANTIL implícita o sin\n"
"garantizar la CONVENIENCIA PARA UN PROPÓSITO PARTICULAR. Véase la Licencia \n"
"Pública General de GNU para más detalles <http://gnu.org/licenses/gpl."
"html>.\n"
"Escrito por Adam Waldenberg."
msgid "Deletions"
msgstr "Borrados"
#, python-format
msgid "Error processing git repository at \"%s\"."
msgstr "Error procesando el repositorio \"%s\""
msgid "HTML output not yet supported in"
msgstr "La salida en HTML no está soportada"
msgid "Hide minor authors"
msgstr "Ocultar los autores menos prolíficos"
msgid "Hide rows with minor work"
msgstr "Ocultar las filas con menor trabajo"
msgid "Insertions"
msgstr "Inserciones"
msgid "Minor Authors"
msgstr "Autores menos prolíficos"
msgid "Modified Rows:"
msgstr "Líneas modificadas:"
msgid "No commited files with the specified extensions were found"
msgstr "No se encontraron archivos comiteados con la extensión especificada"
msgid "No metrics violations were found in the repository"
msgstr "No se encontraron violaciones de la métrica en el repositorio"
#, python-brace-format
msgid "Repository statistics for {0}"
msgstr "Estadísticas del repositorio para {0}"
msgid "Rows"
msgstr "Registros"
msgid "Show minor authors"
msgstr "Mostrar los autores menos prolíficos"
msgid "Show rows with minor work"
msgstr "Mostrar las aportaciones menores"
msgid "Stability"
msgstr "Estabilidad"
#, python-brace-format
msgid "Statistical information for the repository '{0}' was gathered on {1}."
msgstr "Información estadística para el repositorio '{0}' obtenida en {1}."
msgid "Text output not yet supported in"
msgstr "La salida de tipo texto no está soportada"
msgid ""
"The authors with the following emails were excluded from the statistics due "
"to the specified exclusion patterns"
msgstr ""
"Los autores con las siguientes direcciones de correo fueron excluidos según "
"los patrones de exclusión especificados"
msgid "The extensions below were found in the repository history"
msgstr "En el repositorio se encontraron las siguientes extensiones"
msgid ""
"The following authors were excluded from the statistics due to the specified "
"exclusion patterns"
msgstr ""
"Los autores siguientes fueron excluidos de las estadísticas según los "
"patrones de exclusión especificados"
msgid ""
"The following commit revisions were excluded from the statistics due to the "
"specified exclusion patterns"
msgstr ""
"Los siguientes commits fueron excluidos de las estadísticas por los "
"patrones de exclusión especificados"
msgid "The following files are suspiciously big (in order of severity)"
msgstr "Los siguientes ficheros son sospechosamente grandes (listados en "
"orden de severidad)"
msgid ""
"The following files have an elevated cyclomatic complexity (in order of "
"severity)"
msgstr "Los siguientes ficheros tienen una elevada cyclomatic complexity "
"(listados en orden de severidad)"
msgid ""
"The following files have an elevated cyclomatic complexity density (in order "
"of severity)"
msgstr "Los siguientes ficheros tienen una elevada cyclomatic complexity "
"density (listados en orden de severidad)"
msgid ""
"The following files were excluded from the statistics due to the specified "
"exclusion patterns"
msgstr ""
"Los siguientes ficheros fueron excluidos de las estadísticas por los "
"patrones de exclusión especificados"
msgid ""
"The following historical commit information, by author, was found in the "
"repository"
msgstr "Se encontró en el repositorio el siguiente histórico de commits "
"(clasificado por autor)"
msgid "The following history timeline has been gathered from the repository"
msgstr ""
"Se ha obtenido del repositorio la siguiente línea temporal"
msgid ""
"The following responsibilities, by author, were found in the current revision "
"of the repository (comments are excluded from the line count, if possible)"
msgstr ""
"A continuación se presentan para cada autor los ficheros en los que ha "
"tenido mayor responsabilidad o impacto (en la medida de lo posible se han "
"excluido las líneas con comentarios)"
msgid "The given option argument is not a valid boolean."
msgstr "El argumento especificado no es de tipo booleano"
#, python-brace-format
msgid ""
"The output has been generated by {0} {1}. The statistical analysis tool for "
"git repositories."
msgstr ""
"La salida ha sido generada por {0} {1}. La herramienta de análisis "
"estadístico para repositorios git"
#, python-brace-format
msgid "Try `{0} --help' for more information."
msgstr "Teclea '{0} --help' para obtener más información"
msgid "Unable to determine absolute path of git repository."
msgstr "No se ha podido determinar la ruta absoluta al repositorio git"
#, python-brace-format
msgid ""
"Usage: {0} [OPTION]... [REPOSITORY]\n"
"List information about the repository in REPOSITORY. If no repository is\n"
"specified, the current directory is used. If multiple repositories are\n"
"given, information will be fetched from the last repository specified.\n"
"\n"
"Mandatory arguments to long options are mandatory for short options too.\n"
"Boolean arguments can only be given to long options.\n"
" -f, --file-types=EXTENSIONS a comma separated list of file extensions "
"to\n"
" include when computing statistics. The\n"
" default extensions used are:\n"
" {1}\n"
" -F, --format=FORMAT define in which format output should be\n"
" generated; the default format is 'text' "
"and\n"
" the available formats are:\n"
" {2}\n"
" --grading[=BOOL] show statistics and information in a way "
"that\n"
" is formatted for grading of student\n"
" projects; this is the same as supplying "
"the\n"
" options -HlmrTw\n"
" -H, --hard[=BOOL] track rows and look for duplicates harder;\n"
" this can be quite slow with big "
"repositories\n"
" -l, --list-file-types[=BOOL] list all the file extensions available in "
"the\n"
" current branch of the repository\n"
" -L, --localize-output[=BOOL] localize the generated output to the "
"selected\n"
" system language if a translation is\n"
" available\n"
" -m --metrics[=BOOL] include checks for certain metrics during "
"the\n"
" analysis of commits\n"
" -r --responsibilities[=BOOL] show which files the different authors "
"seem\n"
" most responsible for\n"
" --since=DATE only show statistics for commits more "
"recent\n"
" than a specific date\n"
" -T, --timeline[=BOOL] show commit timeline, including author "
"names\n"
" --until=DATE only show statistics for commits older than "
"a\n"
" specific date\n"
" -w, --weeks[=BOOL] show all statistical information in weeks\n"
" instead of in months\n"
" -x, --exclude=PATTERN an exclusion pattern describing the file\n"
" paths, revisions, author names or author\n"
" emails that should be excluded from the\n"
" statistics; can be specified multiple "
"times\n"
" -h, --help display this help and exit\n"
" --version output version information and exit\n"
"\n"
"gitinspector will filter statistics to only include commits that modify,\n"
"add or remove one of the specified extensions, see -f or --file-types for\n"
"more information.\n"
"\n"
"gitinspector requires that the git executable is available in your PATH.\n"
"Report gitinspector bugs to gitinspector@ejwa.se."
msgstr ""
"Uso: {0} [OPCIÓN]... [REPOSITORIO]\n"
"Lista información sobre el repositorio en REPOSITORIO. Si no se especifica \n"
"un repositorio se usará el directorio actual. Si se indican varios \n"
"repositorios, se extraerá la información del último de ellos\n"
"\n"
"Los argumentos obligatorios son necesarios tanto usando las opciones largas \n"
"como las cortas. Los argumentos de tipo booleano solo pueden combinarse \n"
"con opciones largas\n"
" -f, --file-types=EXTENSIONES una lista de extensiones (separadas por"
" \n"
" comas) que se incluirán al extraer "
"las \n"
" estadísticas. Se incluyen por "
"defecto:\n"
" {1}\n"
" Indicando * se incluirán ficheros "
"sin\n"
" extensión; con ** se incluirán todos"
"\n"
" -F, --format=FORMATO define el formato de salida que se "
"quiere \n"
" generar; el formato por defecto es \n"
" 'text' y los formatos disponibles "
"son:\n"
" {2}\n"
" --grading[=BOOLEANO] muestra estadísticas e información \n"
" formateada para valorar el trabajo "
"de\n"
" un estudiante; es equivalente a "
"usar\n"
" las opciones -HlmrTw\n"
" -H, --hard[=BOOLEANO] hace un seguimiento de las filas y "
"busca\n"
" duplicados; este análisis puede ser"
"\n"
" algo lento con repositorios grandes\n"
" -l, --list-file-types[=BOOLEANO] muestra todas las extensiones\n"
" disponibles en la rama actual del\n"
" repositorio\n"
" -L, --localize-output[=BOOLEANO] si está disponible, formatea la salida "
"en\n"
" el idioma del sistema\n"
" -m --metrics[=BOOLEANO] incluye comprobaciones para ciertas \n"
" métricas durante el análisis de los\n"
" commits\n"
" -r --responsibilities[=BOOLEANO] muestra para cada autor cuáles son "
"los\n"
" ficheros en los que parece tener "
"mayor\n"
" participación\n"
" --since=FECHA solo muestra estadísticas para los \n"
" commits realizados desde una fecha\n"
" determinada\n"
" -T, --timeline[=BOOLEANO] muestra los commits en la línea de\n"
" tiempo, autor y nombres\n"
" --until=FECHA solo muestra estadísticas para commits"
"\n"
" realizados hasta la fecha "
"especificada\n"
" -w, --weeks[=BOOLEANO] muestra la información estadística en\n"
" semanas en lugar de meses\n"
" -x, --exclude=PATRÓN un patrón de exclusión para las rutas "
"de\n"
" los ficheros, las revisiones, las\n"
" revisiones con ciertos mensajes de \n"
" commit, los nombres de los autores o "
"sus\n"
" direcciones de correo electrónico "
"que\n"
" se desea excluir; pueden indicarse\n"
" varios patrones\n"
" -h, --help muestra la ayuda y sale\n"
" --version muestra información de la versión y "
"sale\n"
"\n"
"gitinspector filtrará las estadísticas para incluir commits que modifiquen,\n"
"añaden o eliminen una de las extensiones especificadas, ver -f or "
"--file-types para\n"
"obtener más información\n"
"\n"
"gitinspector necesita que el ejecutable git este disponible en el PATH\n"
"Por favor, si encuentras errores en gitinspector ponte en contacto en\n"
"gitinspector@ejwa.se."
msgid ""
"WARNING: The terminal encoding is not correctly configured. gitinspector "
"might malfunction. The encoding can be configured with the environment "
"variable 'PYTHONIOENCODING'."
msgstr ""
"PRECAUCIÓN: La codificación del terminal no está configurada correctamente"
"gitinspector podría no funcionar de forma correcta. La codificación puede "
"configurarse con la variable `PYTHONIOENCODING`"
msgid "XML output not yet supported in"
msgstr "La salida en XML no esta aún soportada"
#, python-brace-format
msgid ""
"gitinspector requires at least Python 2.6 to run (version {0} was found)."
msgstr "Gitinspector requiere al menos Python 2.6 (se ha encontrado la versión"
" {0})"
msgid "invalid regular expression specified"
msgstr "La expresión regular especificada no es válida"
msgid "is mostly responsible for"
msgstr "es principalmente responsable de:"
msgid "specified output format not supported."
msgstr "el formato de salida especificado no está soportado"
#, python-brace-format
msgid "{0} ({1:.3f} in cyclomatic complexity density)"
msgstr "{0} En cyclomatic complexity density: {1:.3f})"
#, python-brace-format
msgid "{0} ({1} estimated lines of code)"
msgstr "{0} ({1} líneas de código estimadas)"
#, python-brace-format
msgid "{0} ({1} in cyclomatic complexity)"
msgstr "{0} (en cyclomatic complexity: {1})"

View File

@ -15,14 +15,14 @@
# You should have received a copy of the GNU General Public License
# along with gitinspector. If not, see <http://www.gnu.org/licenses/>.
#
# Yannick Moy <yannick.moy@gmail.com>, 2014
# Yannick Moy <yannick.moy@gmail.com>, 2014-2015
msgid ""
msgstr ""
"Project-Id-Version: gitinspector 0.4.1\n"
"Project-Id-Version: gitinspector 0.5.0dev\n"
"Report-Msgid-Bugs-To: gitinspector@ejwa.se\n"
"POT-Creation-Date: 2014-11-29 05:20+0100\n"
"PO-Revision-Date: 2014-11-02 08:36+0100\n"
"POT-Creation-Date: 2015-10-02 03:35+0200\n"
"PO-Revision-Date: 2015-10-25 21:56+0100\n"
"Last-Translator: Yannick Moy <yannick.moy@gmail.com>\n"
"Language-Team: French <>\n"
"Language: fr\n"
@ -74,8 +74,8 @@ msgid ""
"Written by Adam Waldenberg."
msgstr ""
"Copyright © 2012-2015 Ejwa Software. Tous droits réservés.\n"
"Licence GPLv3+: GNU GPL version 3 ou ultérieure <http://gnu.org/licenses/"
"gpl.html>.\n"
"Licence GPLv3+: GNU GPL version 3 ou ultérieure <http://gnu.org/licenses/gpl."
"html>.\n"
"Le présent logiciel est un logiciel libre: you pouvez le modifier et le "
"redistributer librement.\n"
"Il n'y a AUCUNE GARANTIE, dans les limites autorisées par la loi.\n"
@ -158,8 +158,8 @@ msgid ""
"The following commit revisions were excluded from the statistics due to the "
"specified exclusion patterns"
msgstr ""
"Les soumissions suivantes ont été exclues des statistiques en raison "
"des filtres d'exclusion spécifiés"
"Les soumissions suivantes ont été exclues des statistiques en raison des "
"filtres d'exclusion spécifiés"
msgid "The following files are suspiciously big (in order of severity)"
msgstr "Les fichiers suivants sont étrangement gros (par ordre d'importance)"
@ -175,8 +175,8 @@ msgid ""
"The following files have an elevated cyclomatic complexity density (in order "
"of severity)"
msgstr ""
"Les fichiers suivants ont une densité de complexité cyclomatique "
trangement élevée (par ordre d'importance)"
"Les fichiers suivants ont une densité de complexité cyclomatique étrangement "
"élevée (par ordre d'importance)"
msgid ""
"The following files were excluded from the statistics due to the specified "
@ -196,8 +196,8 @@ msgid "The following history timeline has been gathered from the repository"
msgstr "La chronologie suivante a été extraite du dépôt"
msgid ""
"The following repsonsibilties, by author, were found in the current revision "
"of the repository (comments are exluded from the line count, if possible)"
"The following responsibilities, by author, were found in the current revision "
"of the repository (comments are excluded from the line count, if possible)"
msgstr ""
"Les responsabilités suivantes, par auteur, ont été extraites de la version "
"courante du dépôt (les commentaires sont exclus du compte des lignes, quand "
@ -211,8 +211,8 @@ msgid ""
"The output has been generated by {0} {1}. The statistical analysis tool for "
"git repositories."
msgstr ""
"Cette sortie a été générée par {0} {1}. L'outil d'analyse statistique "
"pour les dépôts git."
"Cette sortie a été générée par {0} {1}. L'outil d'analyse statistique pour "
"les dépôts git."
#, python-brace-format
msgid "Try `{0} --help' for more information."
@ -221,7 +221,7 @@ msgstr "Entrez `{0} --help' pour plus d'information."
msgid "Unable to determine absolute path of git repository."
msgstr "Impossible de déterminer le chemin absolu du dépôt git."
#, python-brace-format
#, fuzzy, python-brace-format
msgid ""
"Usage: {0} [OPTION]... [REPOSITORY]\n"
"List information about the repository in REPOSITORY. If no repository is\n"
@ -235,6 +235,8 @@ msgid ""
" include when computing statistics. The\n"
" default extensions used are:\n"
" {1}\n"
" Specifying * includes files with no\n"
" extension, while ** includes all files\n"
" -F, --format=FORMAT define in which format output should be\n"
" generated; the default format is 'text' "
"and\n"
@ -273,7 +275,8 @@ msgid ""
" -w, --weeks[=BOOL] show all statistical information in weeks\n"
" instead of in months\n"
" -x, --exclude=PATTERN an exclusion pattern describing the file\n"
" paths, revisions, author names or author\n"
" paths, revisions, revisions with certain\n"
" commit messages, author names or author\n"
" emails that should be excluded from the\n"
" statistics; can be specified multiple "
"times\n"
@ -304,18 +307,23 @@ msgstr ""
" statistiques. Les extensions par défaut\n"
" sont:\n"
" {1}\n"
" Utiliser * pour inclure les fichiers\n"
" sans extension, et ** pour inclure tous\n"
" les fichiers\n"
" -F, --format=FORMAT définit le format de sortie à générer ;\n"
" le format par défaut est 'text' et\n"
" les formats disponibles sont:\n"
" {2}\n"
" --grading[=BOOL] montre les statistiques et les informations\n"
" --grading[=BOOL] montre les statistiques et les "
"informations\n"
" sous un format adapté pour noter des "
"projets\n"
" étudiants ; équivalent aux options "
"-HlmrTw\n"
" étudiants ; équivalent aux options -"
"HlmrTw\n"
" -H, --hard[=BOOL] suit les lignes et cherche les duplications "
"de\n"
" manière plus approfondie ; cela peut être\n"
" manière plus approfondie ; cela peut "
"être\n"
" assez long pour les dépôts très gros\n"
" -l, --list-file-types[=BOOL] énumère les extensions de fichiers "
"disponibles\n"
@ -341,8 +349,11 @@ msgstr ""
" -w, --weeks[=BOOL] montre toutes les informations statistiques "
"en\n"
" semaines plutôt qu'en mois\n"
" -x, --exclude=PATTERN un filtre d'exclusion pour les noms de\n"
" fichiers, soumissions, noms d'auteurs ou\n"
" -x, --exclude=PATTERN un filtre d'exclusion pour les noms de "
"fichiers,\n"
" soumissions, soumissions avec certains\n"
" messages de soumission, noms d'auteurs "
"ou\n"
" e-mails à exclure des statistiques ;\n"
" peut être spécifié plusieurs fois\n"
" -h, --help affiche ce message d'aide et quitte\n"

View File

@ -15,14 +15,14 @@
# You should have received a copy of the GNU General Public License
# along with gitinspector. If not, see <http://www.gnu.org/licenses/>.
#
# Luca Motta <lucamot@gmail.com>, 2014.
# Luca Motta <lucamot@gmail.com>, 2014-2015.
msgid ""
msgstr ""
"Project-Id-Version: gitinspector 0.4.1\n"
"Project-Id-Version: gitinspector 0.5.0dev\n"
"Report-Msgid-Bugs-To: gitinspector@ejwa.se\n"
"POT-Creation-Date: 2014-11-29 05:20+0100\n"
"PO-Revision-Date: 2014-12-01 12:00+0100\n"
"POT-Creation-Date: 2015-10-02 03:35+0200\n"
"PO-Revision-Date: 2015-10-26 08:50+0100\n"
"Last-Translator: Luca Motta <lucamot@gmail.com>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -155,7 +155,7 @@ msgstr "Le seguenti informazioni storiche sui commit, per autore, sono state tro
msgid "The following history timeline has been gathered from the repository"
msgstr "La seguente timeline storica è stata ricavata dal repository"
msgid "The following repsonsibilties, by author, were found in the current revision of the repository (comments are exluded from the line count, if possible)"
msgid "The following responsibilities, by author, were found in the current revision of the repository (comments are excluded from the line count, if possible)"
msgstr "Le seguenti responsabilità, per autore, sono state trovate nella revision corrente del repository (i commenti sono esclusi dal conteggio delle linee, se possibile)"
msgid "The given option argument is not a valid boolean."
@ -185,6 +185,8 @@ msgid ""
" include when computing statistics. The\n"
" default extensions used are:\n"
" {1}\n"
" Specifying * includes files with no\n"
" extension, while ** includes all files\n"
" -F, --format=FORMAT define in which format output should be\n"
" generated; the default format is 'text' and\n"
" the available formats are:\n"
@ -212,7 +214,8 @@ msgid ""
" -w, --weeks[=BOOL] show all statistical information in weeks\n"
" instead of in months\n"
" -x, --exclude=PATTERN an exclusion pattern describing the file\n"
" paths, revisions, author names or author\n"
" paths, revisions, revisions with certain\n"
" commit messages, author names or author\n"
" emails that should be excluded from the\n"
" statistics; can be specified multiple times\n"
" -h, --help display this help and exit\n"
@ -238,6 +241,8 @@ msgstr ""
" statistiche. Le estensioni di default\n"
" usate sono:\n"
" {1}\n"
" Specificando * si includono i file senza\n"
" estensione mentre ** include tutti i file\n"
" -F, --format=FORMAT definisce in che formato deve essere l'output;\n"
" il formato di default è 'text' e quelli\n"
" disponibili sono:\n"
@ -265,7 +270,8 @@ msgstr ""
" -w, --weeks[=BOOL] visualizza tutte le informazioni statistiche\n"
" per settimane anziché per mesi\n"
" -x, --exclude=PATTERN pattern di esclusione che descrivono percorsi\n"
" di file, revision, nomi o email di autori\n"
" di file, revision, revision con determinati\n"
" messaggi, nomi di autori o email di autori\n"
" che devono essere esclusi dalle statistiche;\n"
" può essere specificato più di una volta\n"
" -h, --help visualizza questo messaggio ed esce\n"

View File

@ -15,14 +15,14 @@
# You should have received a copy of the GNU General Public License
# along with gitinspector. If not, see <http://www.gnu.org/licenses/>.
#
# Kamila Chyla <kamila.chyla@gmail.com>, 2014.
# Kamila Chyla <kamila.chyla@gmail.com>, 2015.
msgid ""
msgstr ""
"Project-Id-Version: gitinspector 0.4.1\n"
"Project-Id-Version: gitinspector 0.5.0dev\n"
"Report-Msgid-Bugs-To: gitinspector@ejwa.se\n"
"POT-Creation-Date: 2014-11-29 05:20+0100\n"
"PO-Revision-Date: 2014-12-30 07:50+0100\n"
"POT-Creation-Date: 2015-10-02 03:35+0200\n"
"PO-Revision-Date: 2015-10-24 07:50+0100\n"
"Last-Translator: Kamila Chyla <kamila.chyla@gmail.com>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -155,7 +155,7 @@ msgstr "Informacje o wykonanych komitach, uporządkowane według autorów"
msgid "The following history timeline has been gathered from the repository"
msgstr "Z repozytorium wygenerowano następującą historię zmian"
msgid "The following repsonsibilties, by author, were found in the current revision of the repository (comments are exluded from the line count, if possible)"
msgid "The following responsibilities, by author, were found in the current revision of the repository (comments are excluded from the line count, if possible)"
msgstr "W bieżącej wersji repozytorium zidentyfikowano następujące odpowiedzialności, uporządkowane według autorów (nie uwzględniono, o ile było to możliwe, wierszy zawierających komentarze."
msgid "The given option argument is not a valid boolean."
@ -236,6 +236,8 @@ msgstr ""
" uwzględnionych przy liczeniu statystyk; \n"
" domyślne rozszerzenia to:\n"
" {1}\n"
" Przekazanie * uwzględnia pliki bez rozszerzeń,\n"
" natomiast ** uwzględnia wszystkie pliki.\n"
" -F, --format=FORMAT definiuje format wyściowy raportu;\n"
" domyślnym formatem jest 'text';\n"
" dostępne formaty to:\n"
@ -264,7 +266,8 @@ msgstr ""
" -w, --weeks[=BOOL] pokazuje informacje statystyczne w tygodniach\n"
" zamiast w miesiącach\n"
" -x, --exclude=PATTERN używa wzorca wykluczającego z analizy\n"
" statystycznej nazwy plików, komity, imiona i\n"
" statystycznej nazwy plików, komity, komity"
" zawierające określoną treść, imiona i\n"
" nazwiska, adresy e-mail; opcja może być\n"
" użyta wielokrotnie\n"
" -h, --help wyświetla tę informację i kończy działanie\n"

View File

@ -15,14 +15,14 @@
# You should have received a copy of the GNU General Public License
# along with gitinspector. If not, see <http://www.gnu.org/licenses/>.
#
# Adam Waldenberg <adam.waldenberg@ejwa.se>, 2014.
# Adam Waldenberg <adam.waldenberg@ejwa.se>, 2013-2015.
msgid ""
msgstr ""
"Project-Id-Version: gitinspector 0.4.1\n"
"Project-Id-Version: gitinspector 0.5.0dev\n"
"Report-Msgid-Bugs-To: gitinspector@ejwa.se\n"
"POT-Creation-Date: 2014-11-29 05:20+0100\n"
"PO-Revision-Date: 2014-11-29 06:22+0100\n"
"POT-Creation-Date: 2015-10-02 03:35+0200\n"
"PO-Revision-Date: 2015-10-02 04:12+0200\n"
"Last-Translator: Adam Waldenberg <adam.waldenberg@ejwa.se>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -194,8 +194,8 @@ msgid "The following history timeline has been gathered from the repository"
msgstr "Den följande historiska tidslinjen har samlats in från förrådet"
msgid ""
"The following repsonsibilties, by author, were found in the current revision "
"of the repository (comments are exluded from the line count, if possible)"
"The following responsibilities, by author, were found in the current revision "
"of the repository (comments are excluded from the line count, if possible)"
msgstr ""
"Följande ansvar, utefter upphovsman, hittades i den nuvarande revisionen av "
"förrådet (kommentarer är uteslutna från radberäkningen, om så möjligt)"
@ -226,53 +226,43 @@ msgid ""
"\n"
"Mandatory arguments to long options are mandatory for short options too.\n"
"Boolean arguments can only be given to long options.\n"
" -f, --file-types=EXTENSIONS a comma separated list of file extensions "
"to\n"
" -f, --file-types=EXTENSIONS a comma separated list of file extensions to\n"
" include when computing statistics. The\n"
" default extensions used are:\n"
" {1}\n"
" Specifying * includes files with no\n"
" extension, while ** includes all files\n"
" -F, --format=FORMAT define in which format output should be\n"
" generated; the default format is 'text' "
"and\n"
" generated; the default format is 'text' and\n"
" the available formats are:\n"
" {2}\n"
" --grading[=BOOL] show statistics and information in a way "
"that\n"
" --grading[=BOOL] show statistics and information in a way that\n"
" is formatted for grading of student\n"
" projects; this is the same as supplying "
"the\n"
" projects; this is the same as supplying the\n"
" options -HlmrTw\n"
" -H, --hard[=BOOL] track rows and look for duplicates harder;\n"
" this can be quite slow with big "
"repositories\n"
" -l, --list-file-types[=BOOL] list all the file extensions available in "
"the\n"
" this can be quite slow with big repositories\n"
" -l, --list-file-types[=BOOL] list all the file extensions available in the\n"
" current branch of the repository\n"
" -L, --localize-output[=BOOL] localize the generated output to the "
"selected\n"
" -L, --localize-output[=BOOL] localize the generated output to the selected\n"
" system language if a translation is\n"
" available\n"
" -m --metrics[=BOOL] include checks for certain metrics during "
"the\n"
" -m --metrics[=BOOL] include checks for certain metrics during the\n"
" analysis of commits\n"
" -r --responsibilities[=BOOL] show which files the different authors "
"seem\n"
" -r --responsibilities[=BOOL] show which files the different authors seem\n"
" most responsible for\n"
" --since=DATE only show statistics for commits more "
"recent\n"
" --since=DATE only show statistics for commits more recent\n"
" than a specific date\n"
" -T, --timeline[=BOOL] show commit timeline, including author "
"names\n"
" --until=DATE only show statistics for commits older than "
"a\n"
" -T, --timeline[=BOOL] show commit timeline, including author names\n"
" --until=DATE only show statistics for commits older than a\n"
" specific date\n"
" -w, --weeks[=BOOL] show all statistical information in weeks\n"
" instead of in months\n"
" -x, --exclude=PATTERN an exclusion pattern describing the file\n"
" paths, revisions, author names or author\n"
" paths, revisions, revisions with certain\n"
" commit messages, author names or author\n"
" emails that should be excluded from the\n"
" statistics; can be specified multiple "
"times\n"
" statistics; can be specified multiple times\n"
" -h, --help display this help and exit\n"
" --version output version information and exit\n"
"\n"
@ -293,48 +283,42 @@ msgstr ""
"korta.\n"
"Booleska argument kan bara ges till långa flaggor.\n"
" -f, --file-types=FILSUFFIX en komma-separerad lista av fil-suffix som\n"
" ska inkluderas vid statistikberäkning. "
"De\n"
" ska inkluderas vid statistikberäkning. De\n"
" förvalda suffixen är följande:\n"
" {1}\n"
" Anges * så inkluderas även filer utan\n"
" fil-suffix, medan ** inkluderar alla filer\n"
" oavsett fil-suffix\n"
" -F, --format=FORMAT ange i vilket format den genererade\n"
" utmatningen ska vara; det förvalda "
"formatet\n"
" utmatningen ska vara; det förvalda formatet\n"
" är 'text', de tillgängliga formaten är:\n"
" {2}\n"
" --grading[=BOOL] visa statistik och information anpassad "
"för\n"
" rättning av studentprojekt; detta är\n"
" detsamma som att ange flaggorna -HlmrTw\n"
" -H, --hard[=BOOL] spåra rader och leta efter dubbletter "
"hårdare;\n"
" -H, --hard[=BOOL] spåra rader och leta efter dubbletter hårdare;\n"
" detta kan ta lång tid på stora förråd\n"
" -l, --list-file-types[=BOOL] lista alla fil-suffix som hittades i den\n"
" nuvarande grenen i förrådet\n"
" -L --localize-output[=BOOL] översätt den genererade utmatningen till "
"det\n"
" nuvarande systemspråket om en "
"översättning\n"
" -L --localize-output[=BOOL] översätt den genererade utmatningen till det\n"
" nuvarande systemspråket om en översättning\n"
" finns tillgänglig\n"
" -m --metrics[=BOOL] inkludera kontroller för kodmetrik vid\n"
" analysen av inlämningar\n"
" -r --responsibilities[=BOOL] visa vilka filer olika upphovsmän verkar "
"mest\n"
" -r --responsibilities[=BOOL] visa vilka filer olika upphovsmän verkar mest\n"
" ansvariga för\n"
" --since=DATUM beräkna endast statistik för inlämningar "
"nyare\n"
" --since=DATUM beräkna endast statistik för inlämningar nyare\n"
" än ett angivet datum\n"
" -T, --timeline[=BOOL] visa en inlämningstidslinje för alla funna\n"
" upphovsmän\n"
" --until=DATUM beräkna endast statistik för inlämningar "
"äldre\n"
" --until=DATUM beräkna endast statistik för inlämningar äldre\n"
" än ett angivet datum\n"
" -w, --weeks[=BOOL] visa statistisk information indelad i "
"veckor\n"
" -w, --weeks[=BOOL] visa statistisk information indelad i veckor\n"
" istället för månader\n"
" -x, --exclude=MÖNSTER ett uteslutningsmönster som anger "
"filsökvägar,\n"
" inlämningsrevisioner, upphovsmän eller\n"
" -x, --exclude=MÖNSTER ett uteslutningsmönster som anger filsökvägar,\n"
" inlämningsrevisioner, inlämningsrevisioner\n"
" med angivna kommentarer, upphovsmän eller\n"
" e-post adresser som ska uteslutas ur\n"
" statistiken; kan anges flera gånger\n"
" -h, --help visa denna hjälptext och avsluta\n"

View File

@ -19,15 +19,14 @@
msgid ""
msgstr ""
"Project-Id-Version: gitinspector 0.4.1\n"
"Project-Id-Version: gitinspector 0.5.0dev\n"
"Report-Msgid-Bugs-To: gitinspector@ejwa.se\n"
"POT-Creation-Date: 2014-11-29 05:20+0100\n"
"PO-Revision-Date: 2014-12-03 12:00+0100\n"
"POT-Creation-Date: 2015-10-02 03:35+0200\n"
"PO-Revision-Date: 2015-20-23 18:33-0500\n"
"Last-Translator: Bill Wang <wangzhijiebill@gmail.com>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language-Team: Chinese <>\n"
#, python-format
msgid "% in comments"
@ -64,7 +63,7 @@ msgid ""
"\n"
"Written by Adam Waldenberg."
msgstr ""
"版权所有 © 2012-2015 Ejwa Software. 保留所有权利。\n"
"版权所有 © 2012-2014 Ejwa Software. 保留所有权利。\n"
"GPLv3的许可证+GNU GPL版本3或更高版本<http://gnu.org/licenses/gpl.html>。\n"
"这是自由软件:您可以自由地更改并重新发布它。\n"
"在法律允许的范围内,没有 任何 保证。\n"
@ -130,7 +129,7 @@ msgid "The authors with the following emails were excluded from the statistics d
msgstr "下列邮箱相关联的作者已经从数据统计中移除"
msgid "The extensions below were found in the repository history"
msgstr "在资料库中扎到下列插件"
msgstr "在资料库中找到下列扩展名"
msgid "The following authors were excluded from the statistics due to the specified exclusion patterns"
msgstr "下列作者已按规则从数据统计中移除"
@ -139,13 +138,13 @@ msgid "The following commit revisions were excluded from the statistics due to t
msgstr "下列提交已按规则从数据统计中移除"
msgid "The following files are suspiciously big (in order of severity)"
msgstr "下列文件过大文件有些可疑 (按可疑性排列)"
msgstr "下列文件过大文件有些可疑 (按严重程度排列)"
msgid "The following files have an elevated cyclomatic complexity (in order of severity)"
msgstr "下列文件过度循环性问题,按问题严重性排列"
msgstr "下列文件过度循环性问题 (按严重程度排列)"
msgid "The following files have an elevated cyclomatic complexity density (in order of severity)"
msgstr "下列文件过度循环性密集度问题,按问题严重性排列"
msgstr "下列文件度循环性过度密集 (按严重程度排列)"
msgid "The following files were excluded from the statistics due to the specified exclusion patterns"
msgstr "下列文件已按排除规律移除数据统计"
@ -156,7 +155,7 @@ msgstr "在资料库中找到下列提交记录,已按作者分类"
msgid "The following history timeline has been gathered from the repository"
msgstr "在数据库中收集到下列时间轴"
msgid "The following repsonsibilties, by author, were found in the current revision of the repository (comments are exluded from the line count, if possible)"
msgid "The following responsibilities, by author, were found in the current revision of the repository (comments are excluded from the line count, if possible)"
msgstr "在最新的资料库修改中,找到下列分工,按作者分类。 (在计算代码数量时尽可能的排除了批注)"
msgid "The given option argument is not a valid boolean."
@ -186,6 +185,8 @@ msgid ""
" include when computing statistics. The\n"
" default extensions used are:\n"
" {1}\n"
" Specifying * includes files with no\n"
" extension, while ** includes all files\n"
" -F, --format=FORMAT define in which format output should be\n"
" generated; the default format is 'text' and\n"
" the available formats are:\n"
@ -213,7 +214,8 @@ msgid ""
" -w, --weeks[=BOOL] show all statistical information in weeks\n"
" instead of in months\n"
" -x, --exclude=PATTERN an exclusion pattern describing the file\n"
" paths, revisions, author names or author\n"
" paths, revisions, revisions with certain\n"
" commit messages, author names or author\n"
" emails that should be excluded from the\n"
" statistics; can be specified multiple times\n"
" -h, --help display this help and exit\n"
@ -233,6 +235,8 @@ msgstr ""
"长选项的强制性参数对短选项也适用\n"
"布尔参数只能给予长选项\n"
" -f, --file-types=EXTENSIONS 一串逗号分隔的文件类型\n"
" 用 来包含无扩展名的文件\n"
" 用 包涵所有文件\n"
" 这些文件将会被用于计算统计数据.\n"
" 默认文件类型:\n"
" {1}\n"
@ -254,8 +258,8 @@ msgstr ""
" --until=DATE 只显示特定时间前的结果\n"
" -w, --weeks[=BOOL] 按周来显示统计数据,而非月\n"
" -x, --exclude=PATTERN 按特定格式排除不应该被统计\n"
" 的文件,作者名字或邮箱;可以按文件名,作者名,\n"
" 作者邮箱。可以重复\n"
" 的文件,作者名字或邮箱;可以按文件名,\n"
" 作者名,作者邮箱,提交信息,路径和版本。可以重复\n"
" -h, --help 显示这个帮助信息并退出\n"
" --version 显示版本信息并退出\n"
"\n"
@ -264,7 +268,7 @@ msgstr ""
"\n"
"gitinspector 需要 git 可运行文件 在 PATH 中.\n"
"错误报告,请寄 gitinspector@ejwa.se.\n"
"翻译错误,请寄 wangzhijiebill@gmail.com."
"翻译错误,请寄 wangzhijiebill@gmail.com"
msgid "WARNING: The terminal encoding is not correctly configured. gitinspector might malfunction. The encoding can be configured with the environment variable 'PYTHONIOENCODING'."
msgstr "警告命令指示符编码格式有误。gitinspector可能出错。编码格式可以在环境变量的'PYTHONIOENCODING'下修改"
@ -274,7 +278,7 @@ msgstr "XML文本输出暂不支持"
#, python-brace-format
msgid "gitinspector requires at least Python 2.6 to run (version {0} was found)."
msgstr "gitinspector 要求 Python版本至少2.6 去运行 (已找到版本 {0}) "
msgstr "gitinspector 要求 Python版本至少2.6 (已找到版本 {0}) "
msgid "invalid regular expression specified"
msgstr "无效的正则表达式"

View File

@ -19,15 +19,10 @@
from __future__ import print_function
from __future__ import unicode_literals
from . import localization
localization.init()
try:
import localization
localization.init()
except:
import gitinspector.localization
gitinspector.localization.init()
__version__ = "0.4.1"
__version__ = "0.5.0dev"
__doc__ = _("""Copyright © 2012-2015 Ejwa Software. All rights reserved.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.

64
package.json Normal file
View File

@ -0,0 +1,64 @@
{
"name": "gitinspector",
"version": "0.5.0-dev-1",
"description": "Gitinspector is a statistical analysis tool for git repositories. The default analysis shows general statistics per author, which can be complemented with a timeline analysis that shows the workload and activity of each author.",
"preferGlobal": true,
"main": "gitinspector.py",
"directories": {
"doc": "docs",
"test": "tests"
},
"scripts": {
"clean": "rimraf **/*.pyc",
"crlf": "crlf --set=LF **/*.py",
"prepublish": "npm run clean && npm run crlf",
"release": "with-package git commit -am pkg.version && with-package git tag pkg.version && git push && npm publish && git push --tags",
"release:beta": "npm run release && npm run tag:beta",
"tag:beta": "with-package npm dist-tag add pkg.name@pkg.version beta",
"test": "echo \"Error: no test specified\" && exit 1"
},
"bin": {
"gitinspector": "gitinspector.py"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ejwa/gitinspector.git"
},
"keywords": [
"git",
"statistics",
"stats",
"analytics",
"grading"
],
"author": {
"name": "Adam Waldenberg",
"email": "adam.waldenberg@ejwa.se",
"url": "https://github.com/adam-waldenberg"
},
"contributors": [
"Agustín Cañas",
"Bart van Andel <bavanandel@gmail.com>",
"Bill Wang",
"Christian Kastner",
"Jiwon Kim",
"Kamila Chyla",
"Luca Motta",
"Philipp Nowak",
"Sergei Lomakov",
"Yannick Moy"
],
"license": "GPL-3.0",
"bugs": {
"url": "https://github.com/ejwa/gitinspector/issues"
},
"homepage": "https://github.com/ejwa/gitinspector#readme",
"devDependencies": {
"crlf": "^1.1.0",
"rimraf": "^2.5.4",
"with-package": "^0.2.0"
},
"dependencies": {
"python-shell": "^0.4.0"
}
}