{"action":"created","comment":{"url":"https://api.github.com/repos/translate/pootle/pulls/comments/31381656","id":31381656,"diff_hunk":"@@ -0,0 +1,214 @@\n+#!/usr/bin/env python\n+# -*- coding: utf-8 -*-\n+#\n+# Copyright (C) Pootle contributors.\n+#\n+# This file is a part of the Pootle project. It is distributed under the GPL3\n+# or later license. See the LICENSE file for a copy of the license and the\n+# AUTHORS file for copyright and authorship information.\n+\n+# Some functions are taken from or modelled on the version management in\n+# Django.  Those are:\n+# Copyright (c) Django Software Foundation and individual contributors.  All\n+# rights reserved.\n+\n+from __future__ import unicode_literals\n+\n+import datetime\n+import os\n+import subprocess\n+\n+from django.utils.lru_cache import lru_cache\n+\n+\n+CANDIDATE_MARKERS = ('alpha', 'beta', 'rc', 'final')\n+\n+\n+def get_version(version=None):\n+    \"\"\"Returns a PEP 440-compliant version number from VERSION.\n+\n+    The following examples show a progression from development through\n+    pre-release to release and the resultant versions generated:\n+\n+    >>> get_version((2, 7, 1, 'alpha', 0))\n+    '2.7.1.dev20150530132219'\n+    >>> get_version((2, 7, 1, 'alpha', 1))\n+    '2.7.1a1'\n+    >>> get_version((2, 7, 1, 'beta', 1))\n+    '2.7.1b1'\n+    >>> get_version((2, 7, 1, 'rc', 2))\n+    '2.7.1rc2'\n+    >>> get_version((2, 7, 1, 'final', 0))\n+    '2.7.1'\n+    \"\"\"\n+    version = get_complete_version(version)\n+\n+    # Now build the two parts of the version number:\n+    # main = X.Y[.Z]\n+    # sub = .devN - for pre-alpha releases\n+    #     | {a|b|rc}N - for alpha, beta and rc releases\n+\n+    main = get_main_version(version)\n+    candidate_pos = _get_candidate_pos(version)\n+    candidate = version[candidate_pos]\n+    candidate_extra = version[candidate_pos+1]\n+\n+    sub = ''\n+    if _is_development_candidate(version):\n+        git_changeset = get_git_changeset()\n+        if git_changeset:\n+            sub = '.dev%s' % git_changeset\n+\n+    elif candidate != 'final':\n+        mapping = {'alpha': 'a', 'beta': 'b', 'rc': 'rc'}\n+        sub = mapping[candidate] + str(candidate_extra)\n+\n+    return str(main + sub)\n+\n+\n+def _is_development_candidate(version):\n+    \"\"\"Is this a pre-alpha release\n+\n+    >>> _is_development_candidate((2, 1, 0, 'alpha', 0))\n+    True\n+    >>> _is_development_candidate((2, 1, 0, 'beta', 1))\n+    False\n+    \"\"\"\n+    candidate_pos = _get_candidate_pos(version)\n+    candidate = version[candidate_pos]\n+    candidate_extra = version[candidate_pos+1]\n+    return candidate == 'alpha' and candidate_extra == 0\n+\n+\n+def _get_candidate_pos(version):\n+    \"\"\"Returns the position of the candidate marker.\n+\n+    >>> _get_candidate_pos((1, 2, 0, 'alpha', 0))\n+    3\n+    \"\"\"\n+    return [i for i, part in enumerate(version)\n+            if part in CANDIDATE_MARKERS][0]\n+\n+\n+def _get_candidate(version):\n+    \"\"\"Returns the candidate. One of alpha, beta, rc or final.\n+\n+    >>> _get_candidate((0, 1, 2, 'rc', 1))\n+    'rc'\n+    \"\"\"\n+    return version[_get_candidate_pos(version)]\n+\n+\n+def _get_version_string(parts):\n+    \"\"\"Returns an X.Y.Z version from the list of version parts.\n+\n+    >>> _get_version_string((1, 1, 0))\n+    '1.1.0'\n+    >>> _get_version_string((1, 1, 0, 1))\n+    '1.1.0.1'\n+    \"\"\"\n+    return '.'.join(str(x) for x in parts)\n+\n+\n+def get_main_version(version=None):\n+    \"\"\"Returns main version (X.Y[.Z]) from VERSION.\n+\n+    >>> get_main_version((1, 2, 3, 'alpha', 1))\n+    '1.2.3'\n+    \"\"\"\n+    version = get_complete_version(version)\n+    candidate_pos = _get_candidate_pos(version)\n+    return _get_version_string(version[:candidate_pos])\n+\n+\n+def get_major_minor_version(version=None):\n+    \"\"\"Returns X.Y from VERSION.\n+\n+    >>> get_major_minor_version((1, 2, 3, 'final', 0))\n+    '1.2'\n+    \"\"\"\n+    version = get_complete_version(version)\n+    return _get_version_string(version[:2])\n+\n+\n+def get_complete_version(version=None):\n+    \"\"\"Returns a tuple of the Pootle version. Or the supplied ``version``\n+\n+    >>> get_complete_version()\n+    (1, 2, 3, 'alpha', 0)\n+    \"\"\"\n+    if version is None:\n+        from pootle import VERSION as version\n+\n+    return version\n+\n+\n+def get_docs_version(version=None, positions=2):\n+    \"\"\"Return the version used in documentation.\n+\n+    >>> get_docs_version((1, 2, 1, 'alpha', 0))\n+    'dev'\n+    >>> get_docs_version((1, 2, 1, 'rc', 2))\n+    '1.2'\n+    \"\"\"\n+    version = get_complete_version(version)\n+    candidate = _get_candidate(version)\n+    candidate_pos = _get_candidate_pos(version)\n+    if positions > candidate_pos:\n+        positions = candidate_pos\n+    if _is_development_candidate(version):\n+        return 'dev'\n+    return _get_version_string(version[:positions])\n+\n+\n+@lru_cache()\n+def _shell_command(command):\n+    \"\"\"Return the first result of a shell ``command``\"\"\"\n+    repo_dir = os.path.dirname(os.path.abspath(__file__))\n+\n+    command_subprocess = subprocess.Popen(\n+        command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True,\n+        cwd=repo_dir, universal_newlines=True\n+    )\n+\n+    return command_subprocess.communicate()[0]\n+\n+\n+def get_git_changeset():\n+    \"\"\"Returns a numeric identifier of the latest git changeset.\n+\n+    The result is the UTC timestamp of the changeset in YYYYMMDDHHMMSS format.\n+    This value isn't guaranteed to be unique, but collisions are very unlikely,\n+    so it's sufficient for generating the development version numbers.\n+\n+    >>> get_git_changset()\n+    20150530132219\n+    \"\"\"\n+    timestamp = _shell_command('git log --pretty=format:%ct --quiet -1 HEAD')\n+    try:\n+        timestamp = datetime.datetime.utcfromtimestamp(int(timestamp))\n+    except ValueError:\n+        return None\n+    return timestamp.strftime('%Y%m%d%H%M%S')\n+\n+\n+def get_git_branch():","path":"pootle/core/utils/version.py","position":195,"original_position":195,"commit_id":"bec6ba99b3c5e430882dbeb8cf30aaa7d5d00c28","original_commit_id":"bec6ba99b3c5e430882dbeb8cf30aaa7d5d00c28","user":{"login":"dwaynebailey","id":647438,"avatar_url":"https://avatars.githubusercontent.com/u/647438?v=3","gravatar_id":"","url":"https://api.github.com/users/dwaynebailey","html_url":"https://github.com/dwaynebailey","followers_url":"https://api.github.com/users/dwaynebailey/followers","following_url":"https://api.github.com/users/dwaynebailey/following{/other_user}","gists_url":"https://api.github.com/users/dwaynebailey/gists{/gist_id}","starred_url":"https://api.github.com/users/dwaynebailey/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dwaynebailey/subscriptions","organizations_url":"https://api.github.com/users/dwaynebailey/orgs","repos_url":"https://api.github.com/users/dwaynebailey/repos","events_url":"https://api.github.com/users/dwaynebailey/events{/privacy}","received_events_url":"https://api.github.com/users/dwaynebailey/received_events","type":"User","site_admin":false},"body":"It will allow us to expose git branch on a dev machine.  Its as useful as the hash function at the moment.","created_at":"2015-05-30T15:33:31Z","updated_at":"2015-05-30T15:33:31Z","html_url":"https://github.com/translate/pootle/pull/3773#discussion_r31381656","pull_request_url":"https://api.github.com/repos/translate/pootle/pulls/3773","_links":{"self":{"href":"https://api.github.com/repos/translate/pootle/pulls/comments/31381656"},"html":{"href":"https://github.com/translate/pootle/pull/3773#discussion_r31381656"},"pull_request":{"href":"https://api.github.com/repos/translate/pootle/pulls/3773"}}},"pull_request":{"url":"https://api.github.com/repos/translate/pootle/pulls/3773","id":36562811,"html_url":"https://github.com/translate/pootle/pull/3773","diff_url":"https://github.com/translate/pootle/pull/3773.diff","patch_url":"https://github.com/translate/pootle/pull/3773.patch","issue_url":"https://api.github.com/repos/translate/pootle/issues/3773","number":3773,"state":"open","locked":false,"title":"Use PEP440 versioning","user":{"login":"dwaynebailey","id":647438,"avatar_url":"https://avatars.githubusercontent.com/u/647438?v=3","gravatar_id":"","url":"https://api.github.com/users/dwaynebailey","html_url":"https://github.com/dwaynebailey","followers_url":"https://api.github.com/users/dwaynebailey/followers","following_url":"https://api.github.com/users/dwaynebailey/following{/other_user}","gists_url":"https://api.github.com/users/dwaynebailey/gists{/gist_id}","starred_url":"https://api.github.com/users/dwaynebailey/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dwaynebailey/subscriptions","organizations_url":"https://api.github.com/users/dwaynebailey/orgs","repos_url":"https://api.github.com/users/dwaynebailey/repos","events_url":"https://api.github.com/users/dwaynebailey/events{/privacy}","received_events_url":"https://api.github.com/users/dwaynebailey/received_events","type":"User","site_admin":false},"body":"The brings PEP440 versioning in Pootle.\r\n\r\n* We maintain a tuple ``VERSION`` that is used for versioning throughout Pootle\r\n* The uses of ``sver`` are removed to use ``__version__``\r\n* Docs and POT files use the correct version\r\n* It introduces a number of helpers originally from Django but now quite liberally hacked.  So much so that I wonder if I should have turned this into a Class.\r\n\r\nThe change also include Git helpers to get changeset date, hash and branch.  I haven't looked at using those in e.g. banners when ``POOTLE_INSTANCE_ID = 'development'``","created_at":"2015-05-30T09:11:40Z","updated_at":"2015-05-30T15:33:31Z","closed_at":null,"merged_at":null,"merge_commit_sha":"02d98be6af134e4021488d72be9283e7ac46619d","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/translate/pootle/pulls/3773/commits","review_comments_url":"https://api.github.com/repos/translate/pootle/pulls/3773/comments","review_comment_url":"https://api.github.com/repos/translate/pootle/pulls/comments{/number}","comments_url":"https://api.github.com/repos/translate/pootle/issues/3773/comments","statuses_url":"https://api.github.com/repos/translate/pootle/statuses/bec6ba99b3c5e430882dbeb8cf30aaa7d5d00c28","head":{"label":"dwaynebailey:feature/proper_version","ref":"feature/proper_version","sha":"bec6ba99b3c5e430882dbeb8cf30aaa7d5d00c28","user":{"login":"dwaynebailey","id":647438,"avatar_url":"https://avatars.githubusercontent.com/u/647438?v=3","gravatar_id":"","url":"https://api.github.com/users/dwaynebailey","html_url":"https://github.com/dwaynebailey","followers_url":"https://api.github.com/users/dwaynebailey/followers","following_url":"https://api.github.com/users/dwaynebailey/following{/other_user}","gists_url":"https://api.github.com/users/dwaynebailey/gists{/gist_id}","starred_url":"https://api.github.com/users/dwaynebailey/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dwaynebailey/subscriptions","organizations_url":"https://api.github.com/users/dwaynebailey/orgs","repos_url":"https://api.github.com/users/dwaynebailey/repos","events_url":"https://api.github.com/users/dwaynebailey/events{/privacy}","received_events_url":"https://api.github.com/users/dwaynebailey/received_events","type":"User","site_admin":false},"repo":{"id":10171405,"name":"pootle","full_name":"dwaynebailey/pootle","owner":{"login":"dwaynebailey","id":647438,"avatar_url":"https://avatars.githubusercontent.com/u/647438?v=3","gravatar_id":"","url":"https://api.github.com/users/dwaynebailey","html_url":"https://github.com/dwaynebailey","followers_url":"https://api.github.com/users/dwaynebailey/followers","following_url":"https://api.github.com/users/dwaynebailey/following{/other_user}","gists_url":"https://api.github.com/users/dwaynebailey/gists{/gist_id}","starred_url":"https://api.github.com/users/dwaynebailey/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dwaynebailey/subscriptions","organizations_url":"https://api.github.com/users/dwaynebailey/orgs","repos_url":"https://api.github.com/users/dwaynebailey/repos","events_url":"https://api.github.com/users/dwaynebailey/events{/privacy}","received_events_url":"https://api.github.com/users/dwaynebailey/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/dwaynebailey/pootle","description":"Online translation tool","fork":true,"url":"https://api.github.com/repos/dwaynebailey/pootle","forks_url":"https://api.github.com/repos/dwaynebailey/pootle/forks","keys_url":"https://api.github.com/repos/dwaynebailey/pootle/keys{/key_id}","collaborators_url":"https://api.github.com/repos/dwaynebailey/pootle/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/dwaynebailey/pootle/teams","hooks_url":"https://api.github.com/repos/dwaynebailey/pootle/hooks","issue_events_url":"https://api.github.com/repos/dwaynebailey/pootle/issues/events{/number}","events_url":"https://api.github.com/repos/dwaynebailey/pootle/events","assignees_url":"https://api.github.com/repos/dwaynebailey/pootle/assignees{/user}","branches_url":"https://api.github.com/repos/dwaynebailey/pootle/branches{/branch}","tags_url":"https://api.github.com/repos/dwaynebailey/pootle/tags","blobs_url":"https://api.github.com/repos/dwaynebailey/pootle/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/dwaynebailey/pootle/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/dwaynebailey/pootle/git/refs{/sha}","trees_url":"https://api.github.com/repos/dwaynebailey/pootle/git/trees{/sha}","statuses_url":"https://api.github.com/repos/dwaynebailey/pootle/statuses/{sha}","languages_url":"https://api.github.com/repos/dwaynebailey/pootle/languages","stargazers_url":"https://api.github.com/repos/dwaynebailey/pootle/stargazers","contributors_url":"https://api.github.com/repos/dwaynebailey/pootle/contributors","subscribers_url":"https://api.github.com/repos/dwaynebailey/pootle/subscribers","subscription_url":"https://api.github.com/repos/dwaynebailey/pootle/subscription","commits_url":"https://api.github.com/repos/dwaynebailey/pootle/commits{/sha}","git_commits_url":"https://api.github.com/repos/dwaynebailey/pootle/git/commits{/sha}","comments_url":"https://api.github.com/repos/dwaynebailey/pootle/comments{/number}","issue_comment_url":"https://api.github.com/repos/dwaynebailey/pootle/issues/comments{/number}","contents_url":"https://api.github.com/repos/dwaynebailey/pootle/contents/{+path}","compare_url":"https://api.github.com/repos/dwaynebailey/pootle/compare/{base}...{head}","merges_url":"https://api.github.com/repos/dwaynebailey/pootle/merges","archive_url":"https://api.github.com/repos/dwaynebailey/pootle/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/dwaynebailey/pootle/downloads","issues_url":"https://api.github.com/repos/dwaynebailey/pootle/issues{/number}","pulls_url":"https://api.github.com/repos/dwaynebailey/pootle/pulls{/number}","milestones_url":"https://api.github.com/repos/dwaynebailey/pootle/milestones{/number}","notifications_url":"https://api.github.com/repos/dwaynebailey/pootle/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/dwaynebailey/pootle/labels{/name}","releases_url":"https://api.github.com/repos/dwaynebailey/pootle/releases{/id}","created_at":"2013-05-20T11:29:39Z","updated_at":"2015-05-08T12:27:15Z","pushed_at":"2015-05-30T14:01:34Z","git_url":"git://github.com/dwaynebailey/pootle.git","ssh_url":"git@github.com:dwaynebailey/pootle.git","clone_url":"https://github.com/dwaynebailey/pootle.git","svn_url":"https://github.com/dwaynebailey/pootle","homepage":"http://pootle.translatehouse.org","size":67300,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"translate:master","ref":"master","sha":"f6fe7581dc29af30eb64812cce4e5be789dbb2c8","user":{"login":"translate","id":1538178,"avatar_url":"https://avatars.githubusercontent.com/u/1538178?v=3","gravatar_id":"","url":"https://api.github.com/users/translate","html_url":"https://github.com/translate","followers_url":"https://api.github.com/users/translate/followers","following_url":"https://api.github.com/users/translate/following{/other_user}","gists_url":"https://api.github.com/users/translate/gists{/gist_id}","starred_url":"https://api.github.com/users/translate/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/translate/subscriptions","organizations_url":"https://api.github.com/users/translate/orgs","repos_url":"https://api.github.com/users/translate/repos","events_url":"https://api.github.com/users/translate/events{/privacy}","received_events_url":"https://api.github.com/users/translate/received_events","type":"Organization","site_admin":false},"repo":{"id":3769702,"name":"pootle","full_name":"translate/pootle","owner":{"login":"translate","id":1538178,"avatar_url":"https://avatars.githubusercontent.com/u/1538178?v=3","gravatar_id":"","url":"https://api.github.com/users/translate","html_url":"https://github.com/translate","followers_url":"https://api.github.com/users/translate/followers","following_url":"https://api.github.com/users/translate/following{/other_user}","gists_url":"https://api.github.com/users/translate/gists{/gist_id}","starred_url":"https://api.github.com/users/translate/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/translate/subscriptions","organizations_url":"https://api.github.com/users/translate/orgs","repos_url":"https://api.github.com/users/translate/repos","events_url":"https://api.github.com/users/translate/events{/privacy}","received_events_url":"https://api.github.com/users/translate/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/translate/pootle","description":"Online translation tool","fork":false,"url":"https://api.github.com/repos/translate/pootle","forks_url":"https://api.github.com/repos/translate/pootle/forks","keys_url":"https://api.github.com/repos/translate/pootle/keys{/key_id}","collaborators_url":"https://api.github.com/repos/translate/pootle/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/translate/pootle/teams","hooks_url":"https://api.github.com/repos/translate/pootle/hooks","issue_events_url":"https://api.github.com/repos/translate/pootle/issues/events{/number}","events_url":"https://api.github.com/repos/translate/pootle/events","assignees_url":"https://api.github.com/repos/translate/pootle/assignees{/user}","branches_url":"https://api.github.com/repos/translate/pootle/branches{/branch}","tags_url":"https://api.github.com/repos/translate/pootle/tags","blobs_url":"https://api.github.com/repos/translate/pootle/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/translate/pootle/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/translate/pootle/git/refs{/sha}","trees_url":"https://api.github.com/repos/translate/pootle/git/trees{/sha}","statuses_url":"https://api.github.com/repos/translate/pootle/statuses/{sha}","languages_url":"https://api.github.com/repos/translate/pootle/languages","stargazers_url":"https://api.github.com/repos/translate/pootle/stargazers","contributors_url":"https://api.github.com/repos/translate/pootle/contributors","subscribers_url":"https://api.github.com/repos/translate/pootle/subscribers","subscription_url":"https://api.github.com/repos/translate/pootle/subscription","commits_url":"https://api.github.com/repos/translate/pootle/commits{/sha}","git_commits_url":"https://api.github.com/repos/translate/pootle/git/commits{/sha}","comments_url":"https://api.github.com/repos/translate/pootle/comments{/number}","issue_comment_url":"https://api.github.com/repos/translate/pootle/issues/comments{/number}","contents_url":"https://api.github.com/repos/translate/pootle/contents/{+path}","compare_url":"https://api.github.com/repos/translate/pootle/compare/{base}...{head}","merges_url":"https://api.github.com/repos/translate/pootle/merges","archive_url":"https://api.github.com/repos/translate/pootle/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/translate/pootle/downloads","issues_url":"https://api.github.com/repos/translate/pootle/issues{/number}","pulls_url":"https://api.github.com/repos/translate/pootle/pulls{/number}","milestones_url":"https://api.github.com/repos/translate/pootle/milestones{/number}","notifications_url":"https://api.github.com/repos/translate/pootle/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/translate/pootle/labels{/name}","releases_url":"https://api.github.com/repos/translate/pootle/releases{/id}","created_at":"2012-03-19T23:06:03Z","updated_at":"2015-05-29T16:11:41Z","pushed_at":"2015-05-30T14:01:36Z","git_url":"git://github.com/translate/pootle.git","ssh_url":"git@github.com:translate/pootle.git","clone_url":"https://github.com/translate/pootle.git","svn_url":"https://github.com/translate/pootle","homepage":"http://pootle.translatehouse.org","size":77940,"stargazers_count":450,"watchers_count":450,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":137,"mirror_url":null,"open_issues_count":392,"forks":137,"open_issues":392,"watchers":450,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/translate/pootle/pulls/3773"},"html":{"href":"https://github.com/translate/pootle/pull/3773"},"issue":{"href":"https://api.github.com/repos/translate/pootle/issues/3773"},"comments":{"href":"https://api.github.com/repos/translate/pootle/issues/3773/comments"},"review_comments":{"href":"https://api.github.com/repos/translate/pootle/pulls/3773/comments"},"review_comment":{"href":"https://api.github.com/repos/translate/pootle/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/translate/pootle/pulls/3773/commits"},"statuses":{"href":"https://api.github.com/repos/translate/pootle/statuses/bec6ba99b3c5e430882dbeb8cf30aaa7d5d00c28"}}}}