Repository: IonicaBizau/git-stats Branch: master Commit: 52c56f178297 Files: 15 Total size: 254.6 KB Directory structure: gitextract_dbna9pyy/ ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── CONTRIBUTING.md ├── DOCUMENTATION.md ├── LICENSE ├── MIGRATION.md ├── README.md ├── bin/ │ └── git-stats ├── example/ │ ├── author-stats.js │ └── index.js ├── lib/ │ └── index.js ├── out.html ├── package.json └── scripts/ ├── init-git-post-commit └── migration/ └── 2.0.0.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ github: ionicabizau patreon: ionicabizau open_collective: ionicabizau custom: https://www.buymeacoffee.com/h96wwchmy ================================================ FILE: .gitignore ================================================ *.swp *.swo *~ *.log node_modules *.env .DS_Store package-lock.json .bloggify/* ================================================ FILE: CONTRIBUTING.md ================================================ # 🌟 Contributing Want to contribute to this project? Great! Please read these quick steps to streamline the process and avoid unnecessary tasks. ✨ ## 💬 Discuss Changes Start by opening an issue in the repository using the [bug tracker][1]. Describe your proposed contribution or the bug you've found. If relevant, include platform info and screenshots. 🖼️ Wait for feedback before proceeding unless the fix is straightforward, like a typo. 📝 ## 🔧 Fixing Issues Fork the project and create a branch for your fix, naming it `some-great-feature` or `some-issue-fix`. Commit changes while following the [code style][2]. If the project has tests, add one. ✅ If a `package.json` or `bower.json` exists, add yourself to the `contributors` array; create it if it doesn't. 🙌 ```json { "contributors": [ "Your Name (http://your.website)" ] } ``` ## 📬 Creating a Pull Request Open a pull request and reference the initial issue (e.g., *fixes #*). Provide a clear title and consider adding visual aids for clarity. 📊 ## ⏳ Wait for Feedback Your contributions will be reviewed. If feedback is given, update your branch as needed, and the pull request will auto-update. 🔄 ## 🎉 Everyone Is Happy! Your contributions will be merged, and everyone will appreciate your effort! 😄❤️ Thanks! 🤩 [1]: /issues [2]: https://github.com/IonicaBizau/code-style ================================================ FILE: DOCUMENTATION.md ================================================ ## Documentation You can see below the API reference of this module. ### `GitStats(dataPath)` #### Params - **String** `dataPath`: Path to the data file. #### Return - **GitStats** The `GitStats` instance. ### `getConfig(callback)` Fetches the configuration object from file (`~/.git-stats-config.js`). #### Params - **Function** `callback`: The callback function. #### Return - **Object|Undefined** If no callback is provided, the configuration object will be returned. ### `initConfig(input, callback)` Inits the configuration field (`this.config`). #### Params - **Object|String** `input`: The path to a custom git-stats configuration file or the configuration object. - **Function** `callback`: The callback function. ### `record(data, callback)` Records a new commit. #### Params - **Object** `data`: The commit data containing: - `date` (String|Date): The date object or a string in a format that can be parsed. - `url` (String): The repository remote url. - `hash` (String): The commit hash. - `_data` (Object): If this field is provided, it should be the content of the git-stats data file as object. It will be modified in-memory and then returned. - `save` (Boolean): If `false`, the result will *not* be saved in the file. - **Function** `callback`: The callback function. #### Return - **GitStats** The `GitStats` instance. ### `record(data, callback)` removeCommit Deletes a specifc commit from the history. #### Params - **Object** `data`: The commit data containing: - `date` (String|Date): The date object or a string in a format that can be parsed. If not provided, the hash object will be searched in all dates. - `hash` (String): The commit hash. - `_data` (Object): If this field is provided, it should be the content of the git-stats data file as object. It will be modified in-memory and then returned. - `save` (Boolean): If `false`, the result will *not* be saved in the file. - **Function** `callback`: The callback function. #### Return - **GitStats** The `GitStats` instance. ### `get(callback)` Gets the git stats. #### Params - **Function** `callback`: The callback function. #### Return - **GitStats** The `GitStats` instance. ### `save(stats, callback)` Saves the provided stats. #### Params - **Object** `stats`: The stats to be saved. - **Function** `callback`: The callback function. #### Return - **GitStats** The `GitStats` instance. ### `iterateDays(data, callback)` Iterate through the days, calling the callback function on each day. #### Params - **Object** `data`: An object containing the following fields: - `start` (Moment): A `Moment` date object representing the start date (default: *an year ago*). - `end` (Moment): A `Moment` date object representing the end date (default: *now*). - `format` (String): The format of the date (default: `"MMM D, YYYY"`). - **Function** `callback`: The callback function called with the current day formatted (type: string) and the `Moment` date object. #### Return - **GitStats** The `GitStats` instance. ### `graph(data, callback)` Creates an object with the stats on the provided period (default: *last year*). #### Params - **Object** `data`: The object passed to the `iterateDays` method. - **Function** `callback`: The callback function. #### Return - **GitStats** The `GitStats` instance. ### `calendar(data, callback)` Creates the calendar data for the provided period (default: *last year*). #### Params - **Object** `data`: The object passed to the `graph` method. - **Function** `callback`: The callback function. #### Return - **GitStats** The `GitStats` instance. ### `ansiCalendar(options, callback)` Creates the ANSI contributions calendar. #### Params - **Object** `options`: The object passed to the `calendar` method. - **Function** `callback`: The callback function. #### Return - **GitStats** The `GitStats` instance. ### `authors(options, callback)` Creates an array with the authors of a git repository. #### Params - **String|Object** `options`: The repo path or an object containing the following fields: - `repo` (String): The repository path. - `start` (String): The start date. - `end` (String): The end date. - **Function** `callback`: The callback function. #### Return - **GitStats** The `GitStats` instance. ### `authorsPie(options, callback)` Creates the authors pie. #### Params - **String|Object** `options`: The repo path or an object containing the following fields: - `repo` (String): The repository path. - `radius` (Number): The pie radius. - `no_ansi` (Boolean): If `true`, the pie will not contain ansi characters. - `raw` (Boolean): If `true`, the raw JSON will be displayed. - **Function** `callback`: The callback function. #### Return - **GitStats** The `GitStats` instance. ### `authorsStats(options, callback)` Creates an array with the authors and their additions/deletions statistics. #### Params - **String|Object** `options`: The repo path or an object containing the following fields: - `repo` (String): The repository path. - `start` (String): The start date. - `end` (String): The end date. - `mode` (String): 'additions', 'deletions', or 'both' (default: 'both'). - **Function** `callback`: The callback function. #### Return - **GitStats** The `GitStats` instance. ### `authorsStatsPie(options, callback)` Creates a pie chart showing author statistics (additions/deletions). #### Params - **String|Object** `options`: The repo path or an object containing the following fields: - `repo` (String): The repository path. - `start` (String): The start date. - `end` (String): The end date. - `mode` (String): 'additions', 'deletions', or 'both' (default: 'both'). - `radius` (Number): The pie radius. - `no_ansi` (Boolean): If `true`, the pie will not contain ansi characters. - `raw` (Boolean): If `true`, the raw JSON will be displayed. - **Function** `callback`: The callback function. #### Return - **GitStats** The `GitStats` instance. ### `globalActivity(options, callback)` Creates the global contributions calendar (all commits made by all committers). #### Params - **String|Object** `options`: The repo path or an object containing the following fields: - `repo` (String): The repository path. - `start` (String): The start date. - `end` (String): The end date. - `theme` (String|Object): The calendar theme. - `raw` (Boolean): If `true`, the raw JSON will be displayed. - **Function** `callback`: The callback function. #### Return - **GitStats** The `GitStats` instance. ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015-25 Ionică Bizău (https://ionicabizau.net) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: MIGRATION.md ================================================ ## Migration This document contains information about how to smoothly migrate the things from a version to another version. ### `1.x.x` to `2.x.x` The big change is the the data schema -- which should be migrated. The old `~/.git-stats` format was: ```json { "": { "": { "": "" } } } ``` In the new version, the remote url is not mandatory anymore. The new format is: ```json { "commits": { "": { "": 1 } } } ``` This is supposed to change when users install the `2.x.x` versions. However, if the migration script fails, you can always run it manually: ```sh ./scripts/migration/2.0.0.js ``` This will modify the `~/.git-stats` file. When using `git-stats` as library, things changed too. The old way was: ```js var GitStats = require("git-stats"); GitStats.ansiCalendar(opts, fn); ``` In `2.x.x`, `GitStats` is a constructor. That allows us to create as many `git-stats` instances we want. ```js var GitStats = require("git-stats"); // Provide a custom data path var gs1 = new GitStats("path/to/some/data.json"); // Use the default (~/.git-stats) var gs2 = new GitStats(); ``` ================================================ FILE: README.md ================================================ [![git-stats](http://i.imgur.com/Q7TQYHx.png)](#) # `$ git-stats` [![Support me on Patreon][badge_patreon]][patreon] [![Buy me a book][badge_amazon]][amazon] [![PayPal][badge_paypal_donate]][paypal-donations] [![Ask me anything](https://img.shields.io/badge/ask%20me-anything-1abc9c.svg)](https://github.com/IonicaBizau/ama) [![Version](https://img.shields.io/npm/v/git-stats.svg)](https://www.npmjs.com/package/git-stats) [![Downloads](https://img.shields.io/npm/dt/git-stats.svg)](https://www.npmjs.com/package/git-stats) [![Get help on Codementor](https://cdn.codementor.io/badges/get_help_github.svg)](https://www.codementor.io/@johnnyb?utm_source=github&utm_medium=button&utm_term=johnnyb&utm_campaign=github) Buy Me A Coffee > Local git statistics including GitHub-like contributions calendars. I'd be curious to see your calendar with all your commits. Ping me on Twitter ([**@IonicaBizau**](https://twitter.com/IonicaBizau)). :smile: Until then, here's my calendar: ![](http://i.imgur.com/PpM0i3v.png "") ## Contents - [Installation](#cloud-installation) - [Usage](#usage) - [Importing and deleting commits](#importing-and-deleting-commits) - [Importing all the commits from GitHub and BitBucket](#importing-all-the-commits-from-github-and-bitbucket) - [What about the GitHub Contributions calendar?](#what-about-the-github-contributions-calendar) - [Documentation](#memo-documentation) - [How to contribute](#yum-how-to-contribute) ## :cloud: Installation You can install the package globally and use it as command line tool: ```sh # Install the package globally npm i -g git-stats # Initialize git hooks # This is for tracking the new commits curl -s https://raw.githubusercontent.com/IonicaBizau/git-stats/master/scripts/init-git-post-commit | bash ``` Then, run `git-stats --help` and see what the CLI tool can do. ``` $ git-stats --help Usage: git-stats [options] Local git statistics including GitHub-like contributions calendars. Options: -r, --raw Outputs a dump of the raw JSON data. -g, --global-activity Shows global activity calendar in the current repository. -d, --data Sets a custom data store file. -l, --light Enables the light theme. -n, --disable-ansi Forces the tool not to use ANSI styles. -M, --stats-mode Mode for author stats: 'additions', 'deletions', or 'both' (default: 'both'). -S, --author-stats Shows a pie chart with author additions/deletions statistics. -A, --author Filter author related contributions in the current repository. -a, --authors Shows a pie chart with the author related contributions in the current repository. -u, --until Optional end date. -s, --since Optional start date. --record Records a new commit. Don't use this unless you are a mad scientist. If you are a developer just use this option as part of the module. -h, --help Displays this help. -v, --version Displays version information. Examples: $ git-stats # Default behavior (stats in the last year) $ git-stats -l # Light mode $ git-stats -s '1 January, 2012' # All the commits from 1 January 2012 to now $ git-stats -s '1 January, 2012' -u '31 December, 2012' # All the commits from 2012 $ git-stats -S # Shows author additions/deletions statistics pie chart $ git-stats -S -M additions # Shows only additions statistics $ git-stats -S -M deletions # Shows only deletions statistics Your commit history is kept in ~/.git-stats by default. You can create ~/.git-stats-config.js to specify different defaults. Documentation can be found at https://github.com/IonicaBizau/git-stats. ``` ## Usage ### Importing and deleting commits I know it's not nice to start your git commit calendar from scratch. That's why I created [`git-stats-importer`](https://github.com/IonicaBizau/git-stats-importer)–a tool which imports or deletes the commits from selected repositories. Check it out here: https://github.com/IonicaBizau/git-stats-importer The usage is simple: ```sh # Install the importer tool $ npm install -g git-stats-importer # Go to the repository you want to import $ cd path/to/my-repository # Import the commits $ git-stats-importer # ...or delete them if that's a dummy repository $ git-stats-importer --delete ``` ### Importing all the commits from GitHub and BitBucket Yes, that's also possible. I [built a tool which downloads and then imports all the commits you have pushed to GitHub and BitBucket](https://github.com/IonicaBizau/repository-downloader)! ```sh # Download the repository downloader $ git clone https://github.com/IonicaBizau/repository-downloader.git # Go to repository downloader $ cd repository-downloader # Install the dependencies $ npm install # Start downloading and importing $ ./start ``` ### What about the GitHub Contributions calendar? If you want to visualize the calendars that appear on GitHub profiles, you can do that using [`ghcal`](https://github.com/IonicaBizau/ghcal). ```sh # Install ghcal $ npm install -g ghcal # Check out @alysonla's contributions $ ghcal -u alysonla ``` For more detailed documentation, check out the repository: https://github.com/IonicaBizau/ghcal. If want to get even more GitHub stats in your terminal, you may want to try [`github-stats`](https://github.com/IonicaBizau/github-stats)--this is like `git-stats` but with data taken from GitHub. ## Using the configuration file You can tweak the git-stats behavior using a configuration file in your home directory: `~/.git-stats-config.js`. This file should export an object, like below (defaults are listed): ```js module.exports = { // "DARK", "LIGHT" or an object interpreted by IonicaBizau/node-git-stats-colors "theme": "DARK" // The file where the commit hashes will be stored , "path": "~/.git-stats" // [DEPRECATED] First day of the week https://github.com/IonicaBizau/git-stats/issues/121 , first_day: "Sun" // This defaults to *one year ago* // It can be any parsable date , since: undefined // This defaults to *now* // It can be any parsable date , until: undefined // Don't show authors by default // If true, this will enable the authors pie , authors: false // No global activity by default // If true, this will enable the global activity calendar in the current project , global_activity: false }; ``` Since it's a js file, you can `require` any other modules there. ## Saving the data as HTML and images `git-stats --raw` outputs raw JSON format which can be consumed by other tools to generate results such as HTML files or images. [`git-stats-html`](https://github.com/IonicaBizau/git-stats-html) interprets the JSON data and generates an HTML file. Example: ```sh # Install git-stats-html npm install -g git-stats-html # Export the data from the last year (generate out.html) git-stats --raw | git-stats-html -o out.html # Export data since 2015 (save the results in out.html) git-stats --since '1 January 2015' --raw | ./bin/git-stats-html -o out.html --big ``` After we have the HTML file, we can generate an image file using [`pageres`](https://github.com/sindresorhus/pageres) by [**@sindresorhus**](https://github.com/sindresorhus/): ```sh # Install pageres npm install -g pageres-cli # Generate the image from HTML pageres out.html 775x250 ``` ## Cross-platform compatibility `git-stats` is working fine in terminal emulators supporting ANSI styles. It should work fine on Linux and OS X. If you run `git-stats` to display graph on Windows, please use a terminal that can properly display ANSI colors. Cygwin Terminal is known to work, while Windows Command Prompt and Git Bash do not. Improvements are more than welcome! :dizzy: ## :clipboard: Example Here is an example how to use this package as library. To install it locally, as library, you can use `npm install git-stats` (or `yarn add git-stats`): ```js // Dependencies const GitStats = require("git-stats"); // Create the GitStats instance const g1 = new GitStats(); // Display the ansi calendar g1.ansiCalendar({ theme: "DARK" }, function (err, data) { console.log(err || data); }); ``` ## :memo: Documentation For full API reference, see the [DOCUMENTATION.md][docs] file. ## :question: Get Help There are few ways to get help: 1. Please [post questions on Stack Overflow](https://stackoverflow.com/questions/ask). You can open issues with questions, as long you add a link to your Stack Overflow question. 2. For bug reports and feature requests, open issues. :bug: 3. For direct and quick help, you can [use Codementor](https://www.codementor.io/johnnyb). :rocket: ## :newspaper: Press Highlights - [*A GitHub-like contributions calendar, but locally, with all your git commits*, The Changelog](https://changelog.com/github-like-contributions-calendar-locally-git-commits/) ## :yum: How to contribute Have an idea? Found a bug? See [how to contribute][contributing]. ## :sparkling_heart: Support my projects I open-source almost everything I can, and I try to reply to everyone needing help using these projects. Obviously, this takes time. You can integrate and use these projects in your applications *for free*! You can even change the source code and redistribute (even resell it). However, if you get some profit from this or just want to encourage me to continue creating stuff, there are few ways you can do it: - Starring and sharing the projects you like :rocket: - [![Buy me a book][badge_amazon]][amazon]—I love books! I will remember you after years if you buy me one. :grin: :book: - [![PayPal][badge_paypal]][paypal-donations]—You can make one-time donations via PayPal. I'll probably buy a ~~coffee~~ tea. :tea: - [![Support me on Patreon][badge_patreon]][patreon]—Set up a recurring monthly donation and you will get interesting news about what I'm doing (things that I don't share with everyone). - **Bitcoin**—You can send me bitcoins at this address (or scanning the code below): `1P9BRsmazNQcuyTxEqveUsnf5CERdq35V6` ![](https://i.imgur.com/z6OQI95.png) Thanks! :heart: ## :scroll: License [MIT][license] © [Ionică Bizău][website] [license]: /LICENSE [website]: https://ionicabizau.net [contributing]: /CONTRIBUTING.md [docs]: /DOCUMENTATION.md [badge_patreon]: https://ionicabizau.github.io/badges/patreon.svg [badge_amazon]: https://ionicabizau.github.io/badges/amazon.svg [badge_paypal]: https://ionicabizau.github.io/badges/paypal.svg [badge_paypal_donate]: https://ionicabizau.github.io/badges/paypal_donate.svg [patreon]: https://www.patreon.com/ionicabizau [amazon]: http://amzn.eu/hRo9sIZ [paypal-donations]: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=RVXDDLKKLQRJW ================================================ FILE: bin/git-stats ================================================ #!/usr/bin/env node "use strict"; const Tilda = require("tilda") , GitStatsLib = require("..") , Ul = require("ul") , Moment = require("moment") , Logger = require("bug-killer") , Abs = require("abs") , Typpy = require("typpy") , Package = require("../package") , ReadJson = require("r-json") , IsThere = require("is-there") ; // Constants const GitStats = new GitStatsLib() , CONFIG_PATH = GitStatsLib.CONFIG_PATH , DEFAULT_CONFIG = GitStatsLib.DEFAULT_CONFIG ; try { GitStats.initConfig(); } catch (err) { if (err.code !== "MODULE_NOT_FOUND") { Logger.log("Failed to read the config file:\n" + err.stack, "warn"); } } // Configurations Moment.suppressDeprecationWarnings = true; new Tilda(`${__dirname}/../package.json`, { options: [ { opts: ["record"] , desc: "Records a new commit. Don't use this unless you are a mad scientist. If you are a developer just use this option as part of the module." , name: "data" } , { opts: ["s", "since"] , desc: "Optional start date." , name: "date" , default: GitStats.config.since } , { opts: ["u", "until"] , desc: "Optional end date." , name: "date" , default: GitStats.config.until } , { opts: ["a", "authors"] , desc: "Shows a pie chart with the author related contributions in the current repository." } ,{ opts: ["A", "author"] , desc: "Filter author related contributions in the current repository." } , { opts: ["S", "author-stats"] , desc: "Shows a pie chart with author additions/deletions statistics." } , { opts: ["M", "stats-mode"] , desc: "Mode for author stats: 'additions', 'deletions', or 'both' (default: 'both')." , name: "mode" , default: "both" } , { opts: ["n", "disable-ansi"] , desc: "Forces the tool not to use ANSI styles." } , { opts: ["l", "light"] , desc: "Enables the light theme." } , { opts: ["d", "data"] , desc: "Sets a custom data store file." , name: "path" , default: GitStats.config.path } , { opts: ["g", "global-activity"] , desc: "Shows global activity calendar in the current repository." } , { opts: ["r", "raw"] , desc: "Outputs a dump of the raw JSON data." } ] , examples: [ "git-stats # Default behavior (stats in the last year)" , "git-stats -l # Light mode" , "git-stats -s '1 January, 2012' # All the commits from 1 January 2012 to now" , "git-stats -s '1 January, 2012' -u '31 December, 2012' # All the commits from 2012" , "git-stats -S # Shows author additions/deletions statistics pie chart" , "git-stats -S -M additions # Shows only additions statistics" , "git-stats -S -M deletions # Shows only deletions statistics" ] , notes: "Your commit history is kept in ~/.git-stats by default. You can create ~/.git-stats-config.js to specify different defaults." }).main(action => { let recordOpt = action.options.record , sinceDateOpt = action.options.since , untilDateOpt = action.options.until , authorsOpt = action.options.authors , noAnsiOpt = action.options.disableAnsi , lightOpt = action.options.light , dataPathOpt = action.options.data , globalActivityOpt = action.options.globalActivity , rawOpt = action.options.raw , authorOpt = action.options.author , authorStatsOpt = action.options.authorStats , statsModeOpt = action.options.statsMode ; let options = {}; if(GitStats.config.path) { dataPathOpt.is_provided = true; } if (GitStats.config.authors) { authorsOpt.is_provided = true; } if (GitStats.config.global_activity) { globalActivityOpt.is_provided = true; } // Handle data path if (dataPathOpt.is_provided) { if (IsThere(dataPathOpt.value)) { GitStats.path = Abs(dataPathOpt.value); GitStats.config.data_path = GitStats.path; } else { Logger.log("Cannot find the the specified data path file.", "warn"); } } // --record if (recordOpt.is_provided) { try { options = JSON.parse(recordOpt.value.replace(/^\"|\"$/g, "")); } catch (e) { Logger.log(e, "error"); return process.exit(1); } return GitStats.record(options, function (err) { if (err) { return Logger.log(err, "error"); } process.exit(0); }); } // Create the options options = { start: sinceDateOpt.value ? Moment(sinceDateOpt.value) : Moment().subtract(1, "years") , end: untilDateOpt.value ? Moment(untilDateOpt.value) : Moment() , raw: rawOpt.is_provided }; // Validate the dates if (!options.start || !options.start.isValid()) { options.start = Moment().subtract(1, "years"); Logger.log("Invalid start date. Using default instead (" + options.start.format("LL") + ").", "warn"); } // Handle time range options if (!options.end || !options.end.isValid()) { options.end = Moment(); Logger.log("Invalid end date. Using default instead (" + options.end.format("LL") + ").", "warn"); } // Add the repo path if (authorsOpt.is_provided || globalActivityOpt.is_provided || authorStatsOpt.is_provided) { options.repo = process.cwd(); } // Add the author opt if(authorOpt.is_provided){ options.author = authorOpt.value } // Handle authors if (authorsOpt.is_provided) { options.no_ansi = noAnsiOpt.is_provided; options.radius = (process.stdout.rows / 2) - 4; } // Handle author stats if (authorStatsOpt.is_provided) { options.no_ansi = noAnsiOpt.is_provided; options.radius = (process.stdout.rows / 2) - 4; options.mode = statsModeOpt.value || 'both'; } if (!authorsOpt.is_provided && !authorStatsOpt.is_provided || globalActivityOpt.is_provided) { // This can be a string or an object if (/^object|string$/.test(Typpy(GitStats.config.theme)) && !noAnsiOpt.is_provided && !lightOpt.is_provided) { options.theme = GitStats.config.theme; if (typeof GitStats.config.theme === "string") { if (!/^DARK|LIGHT$/.test(options.theme)) { options.theme = null; } } } else { options.theme = noAnsiOpt.is_provided ? null : lightOpt.is_provided ? "LIGHT": "DARK" ; } } function display (err, data) { if (err) { return Logger.log(err, "error"); } if (typeof data !== "string") { data = JSON.stringify(data); } process.stdout.write(data + "\n"); } if (globalActivityOpt.is_provided) { return GitStats.globalActivity(options, display); } if (authorStatsOpt.is_provided) { return GitStats.authorsStatsPie(options, display); } // Show the graphs GitStats[authorsOpt.is_provided ? "authorsPie" : "ansiCalendar"](options, display); }); ================================================ FILE: example/author-stats.js ================================================ // Dependencies const GitStats = require("../lib"); // Create the GitStats instance const g1 = new GitStats(); console.log("Author Statistics Examples:\n"); // Display author statistics (both additions and deletions) console.log("1. Author statistics (additions + deletions):"); g1.authorsStatsPie({ repo: process.cwd(), mode: "both" }, function (err, data) { console.log(err || data); console.log("\n" + "=".repeat(50) + "\n"); // Display only additions console.log("2. Author statistics (additions only):"); g1.authorsStatsPie({ repo: process.cwd(), mode: "additions" }, function (err, data) { console.log(err || data); console.log("\n" + "=".repeat(50) + "\n"); // Display only deletions console.log("3. Author statistics (deletions only):"); g1.authorsStatsPie({ repo: process.cwd(), mode: "deletions" }, function (err, data) { console.log(err || data); console.log("\n" + "=".repeat(50) + "\n"); // Display raw JSON data console.log("4. Raw JSON data:"); g1.authorsStats({ repo: process.cwd(), mode: "both" }, function (err, data) { console.log(err || JSON.stringify(data, null, 2)); }); }); }); }); ================================================ FILE: example/index.js ================================================ // Dependencies const GitStats = require("../lib"); // Create the GitStats instance const g1 = new GitStats(); // Display the ansi calendar g1.ansiCalendar({ theme: "DARK" }, function (err, data) { console.log(err || data); }); ================================================ FILE: lib/index.js ================================================ "use strict"; const Ul = require("ul") const Abs = require("abs") const ReadJson = require("r-json") const WriteJson = require("w-json") const Moment = require("moment") const Gry = require("gry") const IsThere = require("is-there") const CliPie = require("cli-pie") const CliGhCal = require("cli-gh-cal") const GitLogParser = require("gitlog-parser").parse const ChildProcess = require("child_process") const Deffy = require("deffy") const Typpy = require("typpy") const Spawn = ChildProcess.spawn const IterateObject = require("iterate-object") // Constants const DATE_FORMAT = "MMM D, YYYY" const DEFAULT_STORE = Abs("~/.git-stats") const DEFAULT_DATA = { commits: {} } const CONFIG_PATH = Abs("~/.git-stats-config.js") const LEVELS = [0, 1, 2, 3, 4] class GitStats { /** * GitStats * * @name GitStats * @function * @param {String} dataPath Path to the data file. * @return {GitStats} The `GitStats` instance. */ constructor(dataPath) { this.path = Abs(Deffy(dataPath, DEFAULT_STORE)); this.config = {}; } /** * getConfig * Fetches the configuration object from file (`~/.git-stats-config.js`). * * @name getConfig * @function * @param {Function} callback The callback function. * @return {Object|Undefined} If no callback is provided, the configuration object will be returned. */ getConfig(callback) { let data = {}, err = null; try { data = require(CONFIG_PATH); } catch (err) { if (err.code === "MODULE_NOT_FOUND") { err = null; data = {}; } } if (callback) { return callback(err, data); } else { if (err) { throw err; } } return data; } /** * initConfig * Inits the configuration field (`this.config`). * * @name initConfig * @function * @param {Object|String} input The path to a custom git-stats configuration file or the configuration object. * @param {Function} callback The callback function. */ initConfig(input, callback) { const self = this; if (Typpy(input, Function)) { callback = input; input = null; } input = input || CONFIG_PATH; // Handle object input if (Typpy(input, Object)) { this.config = Ul.deepMerge(input, GitStats.DEFAULT_CONFIG); callback && callback(null, this.config); return this.config; } if (callback) { this.getConfig(function (err, data) { if (err) { return callback(err); } self.initConfig(data, callback); }); } else { this.initConfig(this.getConfig()); } } /** * record * Records a new commit. * * @name record * @function * @param {Object} data The commit data containing: * * - `date` (String|Date): The date object or a string in a format that can be parsed. * - `url` (String): The repository remote url. * - `hash` (String): The commit hash. * - `_data` (Object): If this field is provided, it should be the content of the git-stats data file as object. It will be modified in-memory and then returned. * - `save` (Boolean): If `false`, the result will *not* be saved in the file. * * @param {Function} callback The callback function. * @return {GitStats} The `GitStats` instance. */ record(data, callback) { const self = this; // Validate data callback = callback || function (err) { if (err) throw err; }; data = Object(data); if (typeof data.date === "string") { data.date = new Moment(new Date(data.date)); } if (!/^moment|date$/.test(Typpy(data.date))) { callback(new Error("The date field should be a string or a date object.")); return GitStats; } else if (Typpy(data.date, Date)) { data.date = Moment(data.date); } if (typeof data.hash !== "string" || !data.hash) { callback(new Error("Invalid hash.")); return GitStats; } // This is not used, but remains here just in case we need // it in the future if (typeof data.url !== "string" || !data.url) { delete data.url; } function modify(err, stats) { const commits = stats.commits, day = data.date.format(DATE_FORMAT), today = commits[day] = Object(commits[day]); today[data.hash] = 1; if (data.save === false) { callback(null, stats); } else { self.save(stats, callback); } return stats; } // Check if we have input data if (data._data) { return modify(null, data._data); } else { // Get stats self.get(modify); } return self; } /** * removeCommit * Deletes a specifc commit from the history. * * @name record * @function * @param {Object} data The commit data containing: * * - `date` (String|Date): The date object or a string in a format that can be parsed. If not provided, the hash object will be searched in all dates. * - `hash` (String): The commit hash. * - `_data` (Object): If this field is provided, it should be the content of the git-stats data file as object. It will be modified in-memory and then returned. * - `save` (Boolean): If `false`, the result will *not* be saved in the file. * * @param {Function} callback The callback function. * @return {GitStats} The `GitStats` instance. */ removeCommit(data, callback) { const self = this; // Validate data callback = callback || function (err) { if (err) throw err; }; data = Object(data); if (typeof data.date === "string") { data.date = new Moment(new Date(data.date)); } if (!/^moment|date$/.test(Typpy(data.date))) { data.date = null; } else if (Typpy(data.date, Date)) { data.date = Moment(data.date); } if (typeof data.hash !== "string" || !data.hash) { callback(new Error("Invalid hash.")); return GitStats; } function modify(err, stats) { if (err) { return callback(err); } if (!data.date) { IterateObject(stats.commits, function (todayObj) { delete todayObj[data.hash]; }); } else { const commits = stats.commits, day = data.date.format(DATE_FORMAT), today = commits[day] = Object(commits[day]); delete today[data.hash]; } if (data.save === false) { callback(null, stats); } else { self.save(stats, callback); } return stats; } // Check if we have input data if (data._data) { return modify(null, data._data); } else { // Get stats self.get(modify); } return self; } /** * get * Gets the git stats. * * @name get * @function * @param {Function} callback The callback function. * @return {GitStats} The `GitStats` instance. */ get(callback) { const self = this; ReadJson(self.path, function (err, data) { if (err && err.code === "ENOENT") { return self.save(DEFAULT_DATA, function (err) { callback(err, DEFAULT_DATA); }); } if (err) { return callback(err); } callback(null, data); }); return self; } /** * save * Saves the provided stats. * * @name save * @function * @param {Object} stats The stats to be saved. * @param {Function} callback The callback function. * @return {GitStats} The `GitStats` instance. */ save(stats, callback) { WriteJson(this.path, stats, callback); return this; } /** * iterateDays * Iterate through the days, calling the callback function on each day. * * @name iterateDays * @function * @param {Object} data An object containing the following fields: * * - `start` (Moment): A `Moment` date object representing the start date (default: *an year ago*). * - `end` (Moment): A `Moment` date object representing the end date (default: *now*). * - `format` (String): The format of the date (default: `"MMM D, YYYY"`). * * @param {Function} callback The callback function called with the current day formatted (type: string) and the `Moment` date object. * @return {GitStats} The `GitStats` instance. */ iterateDays(data, callback) { if (typeof data === "function") { callback = data; data = undefined; } // Merge the defaults data.end = data.end || Moment(); data.start = data.start || Moment().subtract(1, "years"); data.format = data.format || DATE_FORMAT; let start = new Moment(data.start.format(DATE_FORMAT), DATE_FORMAT), end = new Moment(data.end.format(DATE_FORMAT), DATE_FORMAT), tomrrow = Moment(end.format(DATE_FORMAT), DATE_FORMAT).add(1, "days"), endStr = tomrrow.format(DATE_FORMAT), cDay = null; while (start.format(DATE_FORMAT) !== endStr) { cDay = start.format(data.format); callback(cDay, start); start.add(1, "days"); } return this; } /** * graph * Creates an object with the stats on the provided period (default: *last year*). * * @name graph * @function * @param {Object} data The object passed to the `iterateDays` method. * @param {Function} callback The callback function. * @return {GitStats} The `GitStats` instance. */ graph(data, callback) { if (typeof data === "function") { callback = data; data = undefined; } const self = this; // Get commits self.get(function (err, stats) { if (err) { return callback(err); } let cDayObj = null, year = {}; // Iterate days self.iterateDays(data, function (cDay) { cDayObj = Object(stats.commits[cDay]); cDayObj = year[cDay] = { _: cDayObj, c: Object.keys(cDayObj).length }; }); callback(null, year); }); return self; } /** * calendar * Creates the calendar data for the provided period (default: *last year*). * * @name calendar * @function * @param {Object} data The object passed to the `graph` method. * @param {Function} callback The callback function. * @return {GitStats} The `GitStats` instance. */ calendar(data, callback) { const self = this; self.graph(data, function (err, graph) { if (err) { return callback(err); } let cal = { total: 0, days: {}, cStreak: 0, lStreak: 0, max: 0, activeDays: 0 }, cDay = null, days = Object.keys(graph), levels = null, cLevel = 0; days.forEach(function (c) { cDay = graph[c]; cal.total += cDay.c; if (cDay.c > cal.max) { cal.max = cDay.c; } if (cDay.c > 0) { cal.activeDays++; if (++cal.cStreak > cal.lStreak) { cal.lStreak = cal.cStreak; } } else { cal.cStreak = 0; } }); levels = cal.max / (LEVELS.length * 2); days.forEach(function (c) { cDay = graph[c]; cal.days[c] = { c: cDay.c, level: !levels ? 0 : (cLevel = Math.round(cDay.c / levels)) >= 4 ? 4 : !cLevel && cDay.c > 0 ? 1 : cLevel }; }); callback(null, cal); }); return self; } /** * ansiCalendar * Creates the ANSI contributions calendar. * * @name ansiCalendar * @function * @param {Object} options The object passed to the `calendar` method. * @param {Function} callback The callback function. * @return {GitStats} The `GitStats` instance. */ ansiCalendar(options, callback) { if (typeof options === "function") { callback = options; options = undefined; } const self = this; // Get calendar data which includes activeDays self.calendar(options, function (err, calendarData) { if (err) { return callback(err); } let cal = [], data = { theme: options.theme, start: options.start, end: options.end, firstDay: options.firstDay // [DEPRECATED] https://github.com/IonicaBizau/git-stats/issues/121 , cal: cal, raw: options.raw, activeDays: calendarData.activeDays }; self.iterateDays(options, function (cDay) { const cDayObj = calendarData.days[cDay]; if (!cDayObj) { return; } cal.push([cDay, cDayObj.c]); }); callback(null, CliGhCal(cal, data)); }); return self; } /** * authors * Creates an array with the authors of a git repository. * * @name authors * @function * @param {String|Object} options The repo path or an object containing the following fields: * * - `repo` (String): The repository path. * - `start` (String): The start date. * - `end` (String): The end date. * * @param {Function} callback The callback function. * @return {GitStats} The `GitStats` instance. */ authors(options, callback) { const repo = new Gry(options.repo); repo.exec(['shortlog', '-s', '-n', '--all', '--since', options.start.toString(), '--until', options.end.toString()], function (err, stdout) { if (err) { return callback(err); } const lines = stdout.split("\n"); const pieData = stdout.split("\n").map(function (c) { const splits = c.split("\t").map(function (cc) { return cc.trim(); }); return { value: parseInt(splits[0]), label: splits[1] }; }); callback(null, pieData); }); return this; } /** * authorsPie * Creates the authors pie. * * @name authorsPie * @function * @param {String|Object} options The repo path or an object containing the following fields: * * - `repo` (String): The repository path. * - `radius` (Number): The pie radius. * - `no_ansi` (Boolean): If `true`, the pie will not contain ansi characters. * - `raw` (Boolean): If `true`, the raw JSON will be displayed. * * @param {Function} callback The callback function. * @return {GitStats} The `GitStats` instance. */ authorsPie(options, callback) { if (typeof options === "string") { options = { repo: options }; } options = Ul.merge(options, { radius: process.stdout.rows / 2 || 20 }); if (!IsThere(options.repo)) { return callback(new Error("The repository folder doesn't exist.")); } let self = this, repo = new Gry(options.repo), pie = null, pieData = []; self.authors(options, function (err, authors) { const maxAuthors = 2 * options.radius; if (err) { return callback(err); } if (authors.length > maxAuthors) { let others = { value: authors.slice(maxAuthors).reduce(function (a, b) { return a + b.value; }, 0), label: "Others" }; authors = authors.slice(0, maxAuthors); authors.push(others); } let data = { legend: true, flat: true, no_ansi: options.no_ansi, authors: authors }; callback(null, options.raw ? data : new CliPie(options.radius, authors, data).toString()); }); return self; } /** * authorsStats * Creates an array with the authors and their additions/deletions statistics. * * @name authorsStats * @function * @param {String|Object} options The repo path or an object containing the following fields: * * - `repo` (String): The repository path. * - `start` (String): The start date. * - `end` (String): The end date. * - `mode` (String): 'additions', 'deletions', or 'both' (default: 'both'). * * @param {Function} callback The callback function. * @return {GitStats} The `GitStats` instance. */ authorsStats(options, callback) { if (typeof options === "string") { options = { repo: options }; } // Set default dates if not provided options = Ul.merge(options, { start: options.start || Moment().subtract(1, "years"), end: options.end || Moment() }); const repo = new Gry(options.repo); const mode = options.mode || 'both'; // Use git log with --numstat to get additions and deletions repo.exec(['log', '--pretty=format:%aN', '--numstat', '--since', options.start.toString(), '--until', options.end.toString()], function (err, stdout) { if (err) { return callback(err); } const lines = stdout.split('\n'); const authorStats = {}; let currentAuthor = null; lines.forEach(function(line) { line = line.trim(); if (!line) return; // Check if this line is an author name (doesn't start with numbers) if (!/^\d/.test(line)) { currentAuthor = line; if (!authorStats[currentAuthor]) { authorStats[currentAuthor] = { additions: 0, deletions: 0 }; } } else if (currentAuthor) { // This is a numstat line (additions, deletions, filename) const parts = line.split('\t'); if (parts.length >= 2) { const additions = parseInt(parts[0]) || 0; const deletions = parseInt(parts[1]) || 0; authorStats[currentAuthor].additions += additions; authorStats[currentAuthor].deletions += deletions; } } }); // Convert to array format and sort by the requested metric const result = Object.keys(authorStats).map(function(author) { const stats = authorStats[author]; let value; let label = author; if (mode === 'additions') { value = stats.additions; label += ` (+${stats.additions})`; } else if (mode === 'deletions') { value = stats.deletions; label += ` (-${stats.deletions})`; } else { value = stats.additions + stats.deletions; label += ` (+${stats.additions}/-${stats.deletions})`; } return { value: value, label: label, author: author, additions: stats.additions, deletions: stats.deletions }; }).filter(function(item) { return item.value > 0; }).sort(function(a, b) { return b.value - a.value; }); callback(null, result); }); return this; } /** * authorsStatsPie * Creates a pie chart showing author statistics (additions/deletions). * * @name authorsStatsPie * @function * @param {String|Object} options The repo path or an object containing the following fields: * * - `repo` (String): The repository path. * - `start` (String): The start date. * - `end` (String): The end date. * - `mode` (String): 'additions', 'deletions', or 'both' (default: 'both'). * - `radius` (Number): The pie radius. * - `no_ansi` (Boolean): If `true`, the pie will not contain ansi characters. * - `raw` (Boolean): If `true`, the raw JSON will be displayed. * * @param {Function} callback The callback function. * @return {GitStats} The `GitStats` instance. */ authorsStatsPie(options, callback) { if (typeof options === "string") { options = { repo: options }; } options = Ul.merge(options, { radius: process.stdout.rows / 2 || 20, mode: 'both' }); if (!IsThere(options.repo)) { return callback(new Error("The repository folder doesn't exist.")); } let self = this; self.authorsStats(options, function (err, authors) { if (err) { return callback(err); } const maxAuthors = 2 * options.radius; if (authors.length > maxAuthors) { let others = { value: authors.slice(maxAuthors).reduce(function (a, b) { return a + b.value; }, 0), label: "Others" }; authors = authors.slice(0, maxAuthors); authors.push(others); } let data = { legend: true, flat: true, no_ansi: options.no_ansi, authors: authors }; callback(null, options.raw ? data : new CliPie(options.radius, authors, data).toString()); }); return self; } /** * globalActivity * Creates the global contributions calendar (all commits made by all committers). * * @name globalActivity * @function * @param {String|Object} options The repo path or an object containing the following fields: * * - `repo` (String): The repository path. * - `start` (String): The start date. * - `end` (String): The end date. * - `theme` (String|Object): The calendar theme. * - `raw` (Boolean): If `true`, the raw JSON will be displayed. * * @param {Function} callback The callback function. * @return {GitStats} The `GitStats` instance. */ globalActivity(options, callback) { if (typeof options === "string") { options = { repo: options }; } options.repo = Abs(options.repo); if (!IsThere(options.repo)) { return callback(new Error("The repository folder doesn't exist.")); } let commits = {}, today = null, cal = []; let logArgs = ["log", "--since", options.start.format(DATE_FORMAT), "--until", options.end.format(DATE_FORMAT)] if (options.author) { logArgs = logArgs.concat(["--author", options.author]) } GitLogParser(Spawn("git", logArgs, { cwd: options.repo }).stdout).on("commit", function (commit) { if (!commit) { return; } today = Moment(commit.date).format(DATE_FORMAT); commits[today] = commits[today] || 0; ++commits[today]; }).on("error", function (err) { callback(err); }).on("finish", function () { Object.keys(commits).forEach(function (c) { cal.push([c, commits[c]]) }); let data = { theme: options.theme, start: options.start, end: options.end, cal: cal, raw: options.raw }; callback(null, CliGhCal(cal, data)); }); return this; } } // Defaults GitStats.CONFIG_PATH = CONFIG_PATH GitStats.DEFAULT_CONFIG = { // Dark theme by default theme: "DARK" // This defaults in library , path: undefined // [DEPRECATED] https://github.com/IonicaBizau/git-stats/issues/121 // This defaults in cli-gh-cal , first_day: undefined // This defaults to *one year ago* , since: undefined // This defaults to *now* , until: undefined // Don't show authors by default , authors: false // No global activity by default , global_activity: false }; module.exports = GitStats; ================================================ FILE: out.html ================================================
Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec Jan Mon Wed Fri
Contributions in the last year 326 total January 1, 2015 – January 23, 2017
Longest streak 58 days January 23, 2017 – January 25, 2015
Current streak 58 days January 23, 2017 – January 23, 2017
================================================ FILE: package.json ================================================ { "bin": { "git-stats": "bin/git-stats" }, "name": "git-stats", "version": "3.5.0", "description": "Local git statistics including GitHub-like contributions calendars.", "main": "lib/index.js", "scripts": { "test": "node test", "postinstall": "node scripts/migration/2.0.0.js" }, "author": "Ionică Bizău (https://ionicabizau.net)", "contributors": [ "Gnab ", "William Boman ", "Fabian Furger ", "Jonah Lawrence ", "Shashank kumar rathour <22csaiml002@jssaten.ac.in>" ], "license": "MIT", "repository": { "type": "git", "url": "https://github.com/IonicaBizau/git-stats.git" }, "keywords": [ "git", "stats", "github", "cli" ], "bugs": { "url": "https://github.com/IonicaBizau/git-stats/issues" }, "homepage": "https://github.com/IonicaBizau/git-stats", "dependencies": { "abs": "^1.3.15", "bug-killer": "^4.0.0", "cli-gh-cal": "^1.4.14", "cli-pie": "^2.4.3", "deffy": "^2.2.5", "gitlog-parser": "0.0.4", "gry": "^6.1.1", "is-there": "^4.5.2", "iterate-object": "^1.3.5", "moment": "^2.30.1", "r-json": "^1.3.1", "tilda": "^4.4.17", "typpy": "^2.4.0", "ul": "^5.2.16", "w-json": "^1.3.11" }, "blah": { "h_img": "http://i.imgur.com/Q7TQYHx.png", "description": [ { "p": "I'd be curious to see your calendar with all your commits. Ping me on Twitter ([**@IonicaBizau**](https://twitter.com/IonicaBizau)). :smile: Until then, here's my calendar:" }, { "img": { "source": "http://i.imgur.com/PpM0i3v.png" } }, { "h2": "Contents" }, { "ul": [ "[Installation](#cloud-installation)", [ "[Usage](#usage)", { "ul": [ "[Importing and deleting commits](#importing-and-deleting-commits)", "[Importing all the commits from GitHub and BitBucket](#importing-all-the-commits-from-github-and-bitbucket)", "[What about the GitHub Contributions calendar?](#what-about-the-github-contributions-calendar)" ] } ], "[Documentation](#memo-documentation)", "[How to contribute](#yum-how-to-contribute)" ] } ], "installation_command": { "language": "sh", "content": [ "# Install the package globally", "npm i -g git-stats", "", "# Initialize git hooks", "# This is for tracking the new commits", "curl -s https://raw.githubusercontent.com/IonicaBizau/git-stats/master/scripts/init-git-post-commit | bash" ] }, "installation": [ { "h2": "Usage" }, { "h3": "Importing and deleting commits" }, { "p": [ "I know it's not nice to start your git commit calendar from scratch. That's why I created [`git-stats-importer`](https://github.com/IonicaBizau/git-stats-importer)–a tool which imports or deletes the commits from selected repositories.", "Check it out here: https://github.com/IonicaBizau/git-stats-importer", "The usage is simple:" ] }, { "code": { "language": "sh", "content": [ "# Install the importer tool", "$ npm install -g git-stats-importer", "", "# Go to the repository you want to import", "$ cd path/to/my-repository", "", "# Import the commits", "$ git-stats-importer", "", "# ...or delete them if that's a dummy repository", "$ git-stats-importer --delete" ] } }, { "h3": "Importing all the commits from GitHub and BitBucket" }, { "p": "Yes, that's also possible. I [built a tool which downloads and then imports all the commits you have pushed to GitHub and BitBucket](https://github.com/IonicaBizau/repository-downloader)!" }, { "code": { "language": "sh", "content": [ "# Download the repository downloader", "$ git clone https://github.com/IonicaBizau/repository-downloader.git", "", "# Go to repository downloader", "$ cd repository-downloader", "", "# Install the dependencies", "$ npm install", "", "# Start downloading and importing", "$ ./start" ] } }, { "h3": "What about the GitHub Contributions calendar?" }, { "p": "If you want to visualize the calendars that appear on GitHub profiles, you can do that using [`ghcal`](https://github.com/IonicaBizau/ghcal)." }, { "code": { "language": "sh", "content": [ "# Install ghcal", "$ npm install -g ghcal", "", "# Check out @alysonla's contributions", "$ ghcal -u alysonla" ] } }, { "p": [ "For more detailed documentation, check out the repository: https://github.com/IonicaBizau/ghcal.", "If want to get even more GitHub stats in your terminal, you may want to try [`github-stats`](https://github.com/IonicaBizau/github-stats)--this is like `git-stats` but with data taken from GitHub." ] }, { "h2": "Using the configuration file" }, { "p": [ "You can tweak the git-stats behavior using a configuration file in your home directory: `~/.git-stats-config.js`.", "This file should export an object, like below (defaults are listed):" ] }, { "code": { "language": "js", "content": [ "module.exports = {", " // \"DARK\", \"LIGHT\" or an object interpreted by IonicaBizau/node-git-stats-colors", " \"theme\": \"DARK\"", "", " // The file where the commit hashes will be stored", " , \"path\": \"~/.git-stats\"", "", " // [DEPRECATED] First day of the week https://github.com/IonicaBizau/git-stats/issues/121", " , first_day: \"Sun\"", "", " // This defaults to *one year ago*", " // It can be any parsable date", " , since: undefined", "", " // This defaults to *now*", " // It can be any parsable date", " , until: undefined", "", " // Don't show authors by default", " // If true, this will enable the authors pie", " , authors: false", "", " // No global activity by default", " // If true, this will enable the global activity calendar in the current project", " , global_activity: false", "};" ] } }, { "p": "Since it's a js file, you can `require` any other modules there." }, { "h2": "Saving the data as HTML and images" }, { "p": [ "`git-stats --raw` outputs raw JSON format which can be consumed by other tools to generate results such as HTML files or images.", "[`git-stats-html`](https://github.com/IonicaBizau/git-stats-html) interprets the JSON data and generates an HTML file. Example:", { "code": { "content": [ "# Install git-stats-html", "npm install -g git-stats-html", "", "# Export the data from the last year (generate out.html)", "git-stats --raw | git-stats-html -o out.html", "", "# Export data since 2015 (save the results in out.html)", "git-stats --since '1 January 2015' --raw | ./bin/git-stats-html -o out.html --big" ], "language": "sh" } }, "After we have the HTML file, we can generate an image file using [`pageres`](https://github.com/sindresorhus/pageres) by [**@sindresorhus**](https://github.com/sindresorhus/):", { "code": { "content": [ "# Install pageres", "npm install -g pageres-cli", "", "# Generate the image from HTML", "pageres out.html 775x250" ], "language": "sh" } } ] }, { "h2": "Cross-platform compatibility" }, { "p": [ "`git-stats` is working fine in terminal emulators supporting ANSI styles. It should work fine on Linux and OS X.", "If you run `git-stats` to display graph on Windows, please use a terminal that can properly display ANSI colors.", "Cygwin Terminal is known to work, while Windows Command Prompt and Git Bash do not. Improvements are more than welcome! :dizzy:" ] } ], "press": { "ul": [ "[*A GitHub-like contributions calendar, but locally, with all your git commits*, The Changelog](https://changelog.com/github-like-contributions-calendar-locally-git-commits/)" ] } }, "files": [ "bin/", "app/", "lib/", "dist/", "src/", "scripts/", "resources/", "menu/", "cli.js", "index.js", "index.d.ts", "package-lock.json", "bloggify.js", "bloggify.json", "bloggify/" ] } ================================================ FILE: scripts/init-git-post-commit ================================================ #!/bin/sh # Check dependencies check_command() { command -v "$1" > /dev/null 2>&1 || { echo "git-stats hook script requires the \`${1}\` binary in order to run." >&2; exit 1; } } check_command "perl" check_command "printf" check_command "git" echo "Setting up git-stats hooks."; # Create a new global templatedir if there are none git_templates_dir=$(git config --global --get init.templatedir); if [ $? -ne 0 ]; then # Create a new global templatedir if there are none git_templates_dir="${HOME}/.git-templates" git config --global init.templatedir "$git_templates_dir" && echo "Set new global git template dir at ${git_templates_dir}" fi git_hooks_dir="${git_templates_dir}/hooks" post_commit_path="${git_hooks_dir}/post-commit" mkdir -p "$git_hooks_dir" # Create the post-commit file content hook=$(cat < "$post_commit_path" \ && chmod +x "$post_commit_path" \ && echo "Successfully set up git-stats hook at ${post_commit_path}." \ && exit 0 else # Remove any previous git-stats hook code blocks perl -i -0pe 's/(([ \t]*# Copy last commit hash to clipboard on commit\s*(\n.*){5}\s*git-stats --record "\$commit_data"\s*)|([ \t]*### git-stats hook \(begin\) ###\s*(\n.*){7}\s*### git-stats hook \(end\) ###\s*))//g' "$post_commit_path" printf "%s\n" "$hook" >> "$post_commit_path" \ && echo "Successfully set up git-stats hook at ${post_commit_path}." \ && exit 0 fi echo "Couldn't set up git-stats hook." exit 1 ================================================ FILE: scripts/migration/2.0.0.js ================================================ #!/usr/bin/env node // Dependencies var ReadJson = require("r-json") , WriteJson = require("w-json") , Abs = require("abs") , Logger = require("bug-killer") ; // Constants const DATA_FILE = Abs("~/.git-stats"); function migrate() { var data = {}; try { data = ReadJson(DATA_FILE) } catch (e) { if (e.code === "ENOENT") { return; } Logger.log(e); } data.commits = data.commits || {}; var newStats = { commits: data.commits }; delete data.commits; Object.keys(data).forEach(function (day) { var cDay = newStats.commits[day] = newStats.commits[day] || {}; Object.keys(data[day]).map(function (c) { Object.keys(data[day][c]).map(function (h) { cDay[h] = 1; }); }); }); WriteJson(DATA_FILE, newStats); } migrate();