Compare commits
120 Commits
Author | SHA1 | Date |
---|---|---|
Adam Waldenberg | b8375442a7 | |
Adam Waldenberg | 6d77989e34 | |
Adam Waldenberg | c80c822892 | |
Adam Waldenberg | b3fbb3e3c3 | |
Bart van Andel | 40cfd0ac84 | |
Bart van Andel | 1e5f5959db | |
Bart van Andel | a8e0de7910 | |
Bart van Andel | 09c5e50381 | |
Bart van Andel | 55434ff76a | |
Bart van Andel | 3d11cdce44 | |
GuillermoMI | 315f4079ac | |
Adam Waldenberg | d0798d8358 | |
Adam Waldenberg | a56680c4b4 | |
Adam Waldenberg | 8cff4bd208 | |
Adam Waldenberg | 983d3d05bd | |
Adam Waldenberg | 9f336e63ce | |
Adam Waldenberg | ba1c3341fe | |
Adam Waldenberg | e543eeaf58 | |
Adam Waldenberg | 6e0365e8ba | |
Adam Waldenberg | 139f5306f4 | |
Adam Waldenberg | 9b5bbc469f | |
Adam Waldenberg | a9d519c93b | |
Adam Waldenberg | 9aa4aba336 | |
Adam Waldenberg | 6ef9936508 | |
Adam Waldenberg | 150e316918 | |
Adam Waldenberg | ba049a0367 | |
Gregrs | 3c48789890 | |
Adam Waldenberg | 533bba64c6 | |
Adam Waldenberg | 4ee02f5907 | |
Adam Waldenberg | 5275521a9a | |
Adam Waldenberg | ecc67a31a5 | |
Adam Waldenberg | 9bd4b979b3 | |
Adam Waldenberg | bfde70db91 | |
Adam Waldenberg | 12f8c8a192 | |
Adam Waldenberg | 9a22763e64 | |
Adam Waldenberg | 346645d655 | |
Adam Waldenberg | 91d94446a7 | |
Adam Waldenberg | 88d840dd51 | |
Adam Waldenberg | 26f77e0ee4 | |
Adam Waldenberg | 4d6ecd3123 | |
Adam Waldenberg | 9b3a5b674e | |
Adam Waldenberg | 87fd5b467f | |
Adam Waldenberg | 4fd918fca4 | |
Adam Waldenberg | 949a301698 | |
Adam Waldenberg | 211060c20e | |
Adam Waldenberg | 9ada057d81 | |
Adam Waldenberg | 7dda8d34b5 | |
Adam Waldenberg | d5106a7302 | |
Adam Waldenberg | 01bdbfaba1 | |
Adam Waldenberg | 7f5b50cd0d | |
Adam Waldenberg | 802f18e7e5 | |
Adam Waldenberg | 7acf871ab1 | |
Adam Waldenberg | 98615ccbfc | |
Adam Waldenberg | 46b21db196 | |
Adam Waldenberg | 3bae1be0cb | |
Adam Waldenberg | 9287b187f7 | |
Adam Waldenberg | dd2feedbe4 | |
Adam Waldenberg | e0941fdcf1 | |
Adam Waldenberg | 109a94e1e7 | |
Adam Waldenberg | 258eefa1e7 | |
Adam Waldenberg | d88ff2c5b9 | |
Adam Waldenberg | c01a59430c | |
Adam Waldenberg | 5af89f798a | |
Adam Waldenberg | d30715cc84 | |
Adam Waldenberg | ce91c4176a | |
Adam Waldenberg | 6606d8b13c | |
Adam Waldenberg | 1ed9b5e3cf | |
Adam Waldenberg | fa04eb57f3 | |
Adam Waldenberg | d941487280 | |
Adam Waldenberg | d8dfecb19a | |
Adam Waldenberg | 4bd723eea0 | |
Adam Waldenberg | 859224f36f | |
Bill Wang | 5ee5127b15 | |
Bill Wang | 75b528a84a | |
Adam Waldenberg | 8a0ba3dca1 | |
Luca Motta | 58a983ed27 | |
Adam Waldenberg | eec6741cfb | |
Yannick Moy | 21c6346868 | |
Adam Waldenberg | 78831c1b9b | |
Kamila Chyla | 79e390afe8 | |
Adam Waldenberg | 81e18ff075 | |
xxyy | b0e7bb5ae9 | |
Adam Waldenberg | b4eb5484ac | |
Adam Waldenberg | bbe07f061d | |
Adam Waldenberg | cff8dd109b | |
Adam Waldenberg | 124636cb85 | |
Adam Waldenberg | e9eab37c83 | |
Marc Harper | 59d9e5c7ae | |
Marc Harper | 4f3e0d3073 | |
Marc Harper | 9abb9b3d56 | |
Adam Waldenberg | f2a4cb92d3 | |
Adam Waldenberg | d333f7bdd2 | |
Adam Waldenberg | 1f2f120389 | |
Adam Waldenberg | 34337dec17 | |
Adam Waldenberg | 6aa41ade9f | |
Adam Waldenberg | fa483b4327 | |
Adam Waldenberg | 5259b76b94 | |
Adam Waldenberg | 4b92e7a3cc | |
Agustín Cañas | 9368898d6d | |
Adam Waldenberg | 38df413ebf | |
Adam Waldenberg | c0cb2d2801 | |
Adam Waldenberg | 07f17f3d2e | |
Adam Waldenberg | 47b11addd9 | |
Adam Waldenberg | aa727a95c3 | |
Adam Waldenberg | 3e88fcb71a | |
Adam Waldenberg | 243e52b5de | |
Adam Waldenberg | e4827ee58e | |
Adam Waldenberg | 6d89cdf8c8 | |
Agustín Cañas | ad36849afb | |
Adam Waldenberg | e3f741b518 | |
Adam Waldenberg | 583b5fa753 | |
Adam Waldenberg | 582ffe7246 | |
Adam Waldenberg | a6c05cc619 | |
Adam Waldenberg | da1553b57e | |
Adam Waldenberg | f368c0019a | |
Adam Waldenberg | 5a18732112 | |
Adam Waldenberg | b4b48deebd | |
Adam Waldenberg | aeb9ad69f9 | |
Adam Waldenberg | 7a9eb69ab0 | |
Adam Waldenberg | f37bdb7c58 |
|
@ -2,5 +2,7 @@ build
|
|||
debian
|
||||
deb_dist
|
||||
dist
|
||||
node_modules
|
||||
*.egg-info
|
||||
*.pyc
|
||||
*.tgz
|
||||
|
|
|
@ -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]
|
||||
|
|
26
CHANGES.txt
26
CHANGES.txt
|
@ -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.
|
||||
|
|
19
README.md
19
README.md
|
@ -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"/>
|
||||
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*.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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\&.
|
||||
|
|
|
@ -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.
|
@ -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>.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
|
@ -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()
|
|
@ -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()
|
||||
|
|
|
@ -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\"> </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>")
|
||||
|
|
|
@ -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\"> </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>")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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__
|
||||
|
|
|
@ -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>")
|
||||
|
|
|
@ -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>")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 |
|
@ -86,15 +86,19 @@
|
|||
}}).each(function() {{
|
||||
$(this).addClass("hoverable");
|
||||
this.innerHTML = "{show_minor_authors} (" + this.hiddenCount + ") ∨";
|
||||
}}).toggle(function() {{
|
||||
this.innerHTML = "{hide_minor_authors} (" + this.hiddenCount + ") ∧";
|
||||
$(this).parent().parent().parent().find("tbody tr").show().each(colorRows);
|
||||
}}, function() {{
|
||||
this.innerHTML = "{show_minor_authors} (" + this.hiddenCount + ") ∨";
|
||||
$(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 + ") ∧";
|
||||
$(this).parent().parent().parent().find("tbody tr").show().each(colorRows);
|
||||
}} else {{
|
||||
this.innerHTML = "{show_minor_authors} (" + this.hiddenCount + ") ∨";
|
||||
$(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 + ") ∨</div>");
|
||||
|
||||
$("div#responsibilities div.button").toggle(function() {{
|
||||
this.innerHTML = "{hide_minor_authors} (" + hiddenResponsibilitiesCount + ") ∧";
|
||||
$("div#responsibilities div").show();
|
||||
}}, function() {{
|
||||
this.innerHTML = "{show_minor_authors} (" + hiddenResponsibilitiesCount + ") ∨";
|
||||
filterResponsibilities();
|
||||
$("div#responsibilities div.button").click(function() {{
|
||||
this.clicked = !this.clicked;
|
||||
if (this.clicked) {{
|
||||
this.innerHTML = "{hide_minor_authors} (" + hiddenResponsibilitiesCount + ") ∧";
|
||||
$("div#responsibilities div").show();
|
||||
}} else {{
|
||||
this.innerHTML = "{show_minor_authors} (" + hiddenResponsibilitiesCount + ") ∨";
|
||||
filterResponsibilities();
|
||||
}}
|
||||
}});
|
||||
}}
|
||||
|
||||
|
@ -119,13 +126,16 @@
|
|||
$("div#timeline table.git tbody tr:visible").each(colorRows);
|
||||
$("div#timeline").prepend("<div class=\"button\">{show_minor_rows} (" + hiddenTimelineCount + ") ∨</div>");
|
||||
|
||||
$("div#timeline div.button").toggle(function() {{
|
||||
this.innerHTML = "{hide_minor_rows} (" + hiddenTimelineCount + ") ∧";
|
||||
$("div#timeline table.git tbody tr").show().each(colorRows);
|
||||
}}, function() {{
|
||||
this.innerHTML = "{show_minor_rows} (" + hiddenTimelineCount + ") ∨";
|
||||
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 + ") ∧";
|
||||
$("div#timeline table.git tbody tr").show().each(colorRows);
|
||||
}} else {{
|
||||
this.innerHTML = "{show_minor_rows} (" + hiddenTimelineCount + ") ∨";
|
||||
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>
|
||||
|
|
|
@ -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.
|
||||
#
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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>")
|
||||
|
|
|
@ -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.
|
|
@ -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\"> </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>")
|
|
@ -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\"> </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>")
|
|
@ -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>")
|
|
@ -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>")
|
|
@ -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>")
|
|
@ -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:
|
|
@ -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>")
|
|
@ -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\"> </div>" + signs[0] * "<div class=\"insert\"> </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>")
|
|
@ -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>")
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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\"> </div>" + signs[0] * "<div class=\"insert\"> </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>")
|
||||
|
|
|
@ -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"
|
||||
|
|
Binary file not shown.
|
@ -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.
|
@ -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})"
|
Binary file not shown.
|
@ -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"
|
||||
|
|
Binary file not shown.
|
@ -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"
|
||||
|
|
Binary file not shown.
|
@ -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"
|
||||
|
|
Binary file not shown.
|
@ -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"
|
||||
|
|
Binary file not shown.
|
@ -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 "无效的正则表达式"
|
||||
|
|
|
@ -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>.
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue