{"id":37072208,"url":"https://github.com/cloudify-cosmo/tattle","last_synced_at":"2026-01-14T08:28:32.870Z","repository":{"id":57473514,"uuid":"46350470","full_name":"cloudify-cosmo/tattle","owner":"cloudify-cosmo","description":"Query your GitHub branches and their related JIRA issues","archived":true,"fork":false,"pushed_at":"2016-03-27T12:46:23.000Z","size":134,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":37,"default_branch":"master","last_synced_at":"2025-09-29T19:35:40.656Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cloudify-cosmo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-11-17T13:57:50.000Z","updated_at":"2023-01-28T13:07:50.000Z","dependencies_parsed_at":"2022-09-26T17:40:54.560Z","dependency_job_id":null,"html_url":"https://github.com/cloudify-cosmo/tattle","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/cloudify-cosmo/tattle","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudify-cosmo%2Ftattle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudify-cosmo%2Ftattle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudify-cosmo%2Ftattle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudify-cosmo%2Ftattle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cloudify-cosmo","download_url":"https://codeload.github.com/cloudify-cosmo/tattle/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudify-cosmo%2Ftattle/sbom","scorecard":{"id":292978,"data":{"date":"2025-08-11","repo":{"name":"github.com/cloudify-cosmo/tattle","commit":"d630fae99c62f15cf14a34f33665211904f4f7fa"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Code-Review","score":0,"reason":"Found 0/29 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Maintained","score":0,"reason":"project is archived","details":["Warn: Repository is archived."],"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 3 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-17T18:45:53.435Z","repository_id":57473514,"created_at":"2025-08-17T18:45:53.435Z","updated_at":"2025-08-17T18:45:53.435Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28414022,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T08:16:59.381Z","status":"ssl_error","status_checked_at":"2026-01-14T08:13:45.490Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-14T08:28:31.298Z","updated_at":"2026-01-14T08:28:32.860Z","avatar_url":"https://github.com/cloudify-cosmo.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Tattle\n\ntattle is a tool that enables you to dig a little deeper in your GitHub projects (and even their related JIRA issues).\n\n\n## Installation\n```\npip install tattle\n```\n## Setup - Setting Environment Variables\n\n### Github API request limitations\nAs part of it's operation, tattle interacts with the GibHub API. For unauthenticated users, GitHub [limits the number of API requests to 60 per hour](https://developer.github.com/v3/#rate-limiting). While this limitation still enables tattle to perform small queries, it is highly recommended to provide tattle with GitHub credentials (username and password) before running it.\n\n### Setting GitHub-related Environment Variables\n\nTo provide tattle with GitHub credentials, simply create two environment variables named `GITHUB_USER` and `GITHUB_PASS`, and set them with a GitHub username and a GitHub password accordingly.\n\nFor example, if your GitHub username is 'octocat' and your GitHub password is 'mypass' then you can create the environment variables (and setup tattle) as follows:\n  \n```\nexport GITHUB_USER=\"octocat\"\nexport GITHUB_PASS=\"mypass\"\n```\n## Usage\n\n### Quick Example Using the CLI: Filtering Branches by Name\n\nAssume that you are part of a team that works on a very large project.\nThis project is a product of years of development, and it contains dozens of repos and hundreds of branches. In these kinds of projects, even if every team member does everything in her power to follow naming conventions and the like, it is almost inevitable this mishaps will occur. And that's where tattle comes at hand.\n\nYou can you use tattle either with a CLI, or with a YAML configurations file. \nThe CLI may not provide all the features tattle offers, but, for simple queries, it makes the whole usage experience more compelling.\n\nNow, let's say we want to get all the branches of the GitHub organization named 'cloudify-cosmo' whose name contains the string 'CFY'.\nIn this case, you'll simply run the following command:\n```\ntattle --branch-names CFY --org cloudify-cosmo\n```\nThis will produce a 'repot.json' file containing all the mentioned above branches.\n\nYou'll notice that the argument '--branch-names' implies that multiple branch names can be entered. Well, that's true. You can enter as many as you'd like, separated by spaces, and the .json report file will contain a union of all the names you've entered.\n\nTattle supports additional CLI arguments; '--config-path', '--jira-team', '--jira-statuses', '--output-path' and '--thread-limit'; all of which you can find an explenation for down below in the The Query Config Section.\n\n### Another Quick Example, This Time Using A YAML Config File: Filtering Branches by Name\n\nConsider this simple config.yaml file:\n```\nquery_config:\n    data_type:        branch\n    github_org:       cloudify-cosmo\n    \nfilters:\n  - type: name\n    regular_expressions: [CFY]\n```\n\nWith the help of this little file, tattle will create a .json report containing all the branches of the GitHub organization named 'cloudify-cosmo' whose name contains the string 'CFY'\n\nWe just need to feed that config file to tattle:\n```\ntattle --config-path=/path/to/config/file\n```\n\n### Let's Go Deeper! - The config.yaml File\n\nTattle's functionality relies on a config.yaml file.\nBefore getting into specifics, let's have a quick look at a sample config file:\n```\n---\nquery_config:\n    thread_limit:     120\n    data_type:        branch\n    github_org:       cloudify-cosmo\n    output_path:      /home/avia/tattle/output/report.json\n\nfilters:\n  - type: name\n    precedence: 1\n    regular_expressions: [CFY]\n        \n  - type: issue\n    precedence: 2\n    jira_team_name: cloudifysource\n    jira_statuses:  [Closed, Resolved]\n    transform:\n        base:               CFY-*\\d+\n        if_doesnt_contain:  '-'\n        replace_from:       CFY\n        replace_to:         CFY-\n...\n```\n#### The Query Config Section\nAs you can see, the first part of this file is somewhat reminicent of the config file of our first example. Let's see what was added at the `query_config` section, and elaborate a little more on it's options.\n\n`thread_limit` - sets the maximum number of threads used by tattle.\n- unless this field is explicitly specified, tattle will use all available resources in order to perform as fast as it can. Before limiting the number\nof threads that tattle uses, keep in mind that interacting with external APIs over the web can take some time, especially when dealing with large GitHub project.\n\n`data_type` - the GitHub data type that is equired by the user.\n* currently, only the `branch` option is available. But there are plans to extand tattle so it will be also able to work on GitHub tags and repositories.\n\n`github_org` - the name of the GitHub organization that we wish to enquire.\n\n`output_path` - the path of tattle's product, the report.json file\n* if an output path is not specified, the report file will be written in the system's tmp directory, under `tattle/report.json`.\n\n#### The Filters Section\n\nThe `filters` part of config.yaml can consist of an unlimited number of filters. Regardless of the filter's type, every filter has two mandatory fields:\n\n`type` - the type of the filter.\n* currently, only `name_filter` and `issue_filter` types are available.\n\n`precedence` - The relative order of the filter. Filters will be applied by their precedence, in ascending order.\n* Tip: assign lower precedence to 'heavier' filters. As a rule of thumb, assign name filters to a higher precedence than issue filters.\n\nname filters contain one additional field:\n\n`regular_expressions` - a list of python styled regular expressions ('regexes'). If the list contains more than one item, the output will be a union of all the regexes. i.e, if the list is `[feature, release]`, the branches whose name includes 'feature' or 'build' (or both) won't be filtered by that name filter.\n* if you never heard of regular expressions, don't worry about it. Filtering in the manner of the above example doesn't require even knowing what regexes are. Just think of the `regular_expressions` field as a list of words that you want in a branch name.\n\nIssue filters are a bit more complex, but I'm sure that if you got until here you'll be just fine. Let's start will the simple stuff:\n\n`cloudifysource` - the name of the JIRA team which contains your project's issues. Simple, ain't it?\n\n`jira_statuses` - a list of all the JIRA status names that won't be filtered by that issue filter. If we refer to the above example, an issue filter whose `jira_statuses` is  `[Closed, Resolved]` will filter all branches whose corresponding JIRA issue is not 'Closed' or 'Resolved'.\n\nBut how do we get from a branch to a JIRA issue? We decided to address this issue by following a convention that is prevelant here at Cloudify. Cloudify's GitHub branches follow the naming convention `CFY-\u003cfeature-number\u003e-\u003cfeature-description\u003e`. [well, actually, *most* of our branches follow this convention. The fact that there are some minor divergences from this convention was one of the reasons to the development of tattle]. Anyway, with this convention at hand, we decided that the name of the corresponding issue of a `CFY-\u003cfeature-number\u003e-\u003cfeature-description\u003e` branch is `CFY-\u003cfeature-number\u003e`.\n\nFrom a broader prespective, what we did is to *transform* the branch's name into it's JIRA issue name. And that's where `transform` comes from. `transform`'s fields, although specifically formulated,  have quite self-explanitory names. Let's start with the first and most basic one:\n\n`base` - a regular expression that is used to extract the (almost final) issue name from a GitHub item (in our case, a branch). Form the example transform above, we get the base `CFY-*\\d+`. This regex transforms the branch name `revert-249-CFY-2504-fix-cfy-status-32m8` (told ya that some branches don't strictly follow our naming conventions) into the issue name `CFY-2504`.\n\nSometimes, providing only a base is enough. This is the case when transforming a branch name into an issue name is simple, or when there are no special edge cases. But for more complex cases, `transform` provides three additional fields: `if_doesnt_contain`, `replace_from`, and `replace_to`.\n\nI will explain all three of them together, with the aid of an example based on real use case that we encountered.\nAssume that you have two branches, `CFY-1001-feature1` and `CFY1002-feature2`. If we provide only the base `CFY-*\\d+`, then `CFY-1001-feature1` branch will be transformed to `CFY-1001` issue, and `CFY1002-feature2` will be transformed to `CFY1002`.\nBut `CFY1002` is not a valid issue name - it is missing the `-`!\nThat's where those three fields come into play:\n```\nif_doesnt_contain:  '-'\nreplace_from:       CFY\nreplace_to:         CFY-\n```\nThey are basiclly saying \"if the base doesn't contain the string `-`, then replace the string `CFY` with the string `CFY-`\". So, in our example, first `base` will turn `CFY1002-feature2` into `CFY1002`, and the three other fields will complete the transform by turning `CFY1002` into `CFY-1002`. That's it. Not so complex when you come to think of it.\n\n\n## The Output\n\nAs mentioned earlier, tattle's output is in the form of a json file.\nThe files contains a list of tattle-styled GitHub branches, each of them includes the following fields:\n\n`name` - the branch's name.\n\n`repo` - the branch's repo name. The repo field itself also contains a field of the repo's GitHub organization (or owner).\n\n`committer_email` - The email of the last conributer to the branch.\n\n`jira_issue` - contains the JIRA issue status related to the branch, and that issue's name.\n\n\n## More to Come\n\nFirst of all, contributions are always welcome.\nMore specifically, our top priorities are to add support for GitHub tags and Github repos.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcloudify-cosmo%2Ftattle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcloudify-cosmo%2Ftattle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcloudify-cosmo%2Ftattle/lists"}