New config hide_project_names
This commit is contained in:
parent
3373ef39e4
commit
71d5eef12a
15 changed files with 165 additions and 17 deletions
|
@ -3,6 +3,13 @@ History
|
|||
-------
|
||||
|
||||
|
||||
10.2.2 (Unreleased)
|
||||
+++++++++++++++++++
|
||||
|
||||
- New config hide_project_name and argument --hide-project-names for
|
||||
obfuscating project names when sending coding activity to api.
|
||||
|
||||
|
||||
10.2.1 (2018-04-26)
|
||||
+++++++++++++++++++
|
||||
|
||||
|
|
|
@ -61,6 +61,7 @@ format. An example config file with all available options::
|
|||
debug = false
|
||||
api_key = your-api-key
|
||||
hide_filenames = false
|
||||
hide_project_names = false
|
||||
exclude =
|
||||
^COMMIT_EDITMSG$
|
||||
^TAG_EDITMSG$
|
||||
|
|
5
tests/samples/configs/paranoid_projects.cfg
Normal file
5
tests/samples/configs/paranoid_projects.cfg
Normal file
|
@ -0,0 +1,5 @@
|
|||
[settings]
|
||||
debug = false
|
||||
api_key = c21f8ebd-6a6a-48a0-900b-0870db3d7afe
|
||||
api_url = https://api.wakatime.com/api/v1/heartbeats
|
||||
hide_project_names = true
|
|
@ -4,8 +4,9 @@ usage: wakatime [-h] [--entity FILE] [--key KEY] [--write] [--plugin PLUGIN]
|
|||
[--proxy PROXY] [--no-ssl-verify] [--project PROJECT]
|
||||
[--alternate-project ALTERNATE_PROJECT] [--language LANGUAGE]
|
||||
[--hostname HOSTNAME] [--disable-offline] [--hide-filenames]
|
||||
[--exclude EXCLUDE] [--exclude-unknown-project]
|
||||
[--include INCLUDE] [--include-only-with-project-file]
|
||||
[--extra-heartbeats] [--log-file LOG_FILE] [--api-url API_URL]
|
||||
[--timeout TIMEOUT] [--config CONFIG] [--verbose] [--version]
|
||||
[--hide-project-names] [--exclude EXCLUDE]
|
||||
[--exclude-unknown-project] [--include INCLUDE]
|
||||
[--include-only-with-project-file] [--extra-heartbeats]
|
||||
[--log-file LOG_FILE] [--api-url API_URL] [--timeout TIMEOUT]
|
||||
[--config CONFIG] [--verbose] [--version]
|
||||
wakatime: error: Missing api key. Find your api key from wakatime.com/settings/api-key.
|
||||
|
|
|
@ -4,8 +4,9 @@ usage: wakatime [-h] [--entity FILE] [--key KEY] [--write] [--plugin PLUGIN]
|
|||
[--proxy PROXY] [--no-ssl-verify] [--project PROJECT]
|
||||
[--alternate-project ALTERNATE_PROJECT] [--language LANGUAGE]
|
||||
[--hostname HOSTNAME] [--disable-offline] [--hide-filenames]
|
||||
[--exclude EXCLUDE] [--exclude-unknown-project]
|
||||
[--include INCLUDE] [--include-only-with-project-file]
|
||||
[--extra-heartbeats] [--log-file LOG_FILE] [--api-url API_URL]
|
||||
[--timeout TIMEOUT] [--config CONFIG] [--verbose] [--version]
|
||||
[--hide-project-names] [--exclude EXCLUDE]
|
||||
[--exclude-unknown-project] [--include INCLUDE]
|
||||
[--include-only-with-project-file] [--extra-heartbeats]
|
||||
[--log-file LOG_FILE] [--api-url API_URL] [--timeout TIMEOUT]
|
||||
[--config CONFIG] [--verbose] [--version]
|
||||
wakatime: error: Missing api key. Find your api key from wakatime.com/settings/api-key.
|
||||
|
|
|
@ -4,8 +4,9 @@ usage: wakatime [-h] [--entity FILE] [--key KEY] [--write] [--plugin PLUGIN]
|
|||
[--proxy PROXY] [--no-ssl-verify] [--project PROJECT]
|
||||
[--alternate-project ALTERNATE_PROJECT] [--language LANGUAGE]
|
||||
[--hostname HOSTNAME] [--disable-offline] [--hide-filenames]
|
||||
[--exclude EXCLUDE] [--exclude-unknown-project]
|
||||
[--include INCLUDE] [--include-only-with-project-file]
|
||||
[--extra-heartbeats] [--log-file LOG_FILE] [--api-url API_URL]
|
||||
[--timeout TIMEOUT] [--config CONFIG] [--verbose] [--version]
|
||||
[--hide-project-names] [--exclude EXCLUDE]
|
||||
[--exclude-unknown-project] [--include INCLUDE]
|
||||
[--include-only-with-project-file] [--extra-heartbeats]
|
||||
[--log-file LOG_FILE] [--api-url API_URL] [--timeout TIMEOUT]
|
||||
[--config CONFIG] [--verbose] [--version]
|
||||
wakatime: error: argument --timeout: invalid int value: 'abc'
|
||||
|
|
|
@ -4,10 +4,11 @@ usage: wakatime [-h] [--entity FILE] [--key KEY] [--write] [--plugin PLUGIN]
|
|||
[--proxy PROXY] [--no-ssl-verify] [--project PROJECT]
|
||||
[--alternate-project ALTERNATE_PROJECT] [--language LANGUAGE]
|
||||
[--hostname HOSTNAME] [--disable-offline] [--hide-filenames]
|
||||
[--exclude EXCLUDE] [--exclude-unknown-project]
|
||||
[--include INCLUDE] [--include-only-with-project-file]
|
||||
[--extra-heartbeats] [--log-file LOG_FILE] [--api-url API_URL]
|
||||
[--timeout TIMEOUT] [--config CONFIG] [--verbose] [--version]
|
||||
[--hide-project-names] [--exclude EXCLUDE]
|
||||
[--exclude-unknown-project] [--include INCLUDE]
|
||||
[--include-only-with-project-file] [--extra-heartbeats]
|
||||
[--log-file LOG_FILE] [--api-url API_URL] [--timeout TIMEOUT]
|
||||
[--config CONFIG] [--verbose] [--version]
|
||||
|
||||
Common interface for the WakaTime api.
|
||||
|
||||
|
@ -49,6 +50,10 @@ optional arguments:
|
|||
--disable-offline Disables offline time logging instead of queuing
|
||||
logged time.
|
||||
--hide-filenames Obfuscate filenames. Will not send file names to api.
|
||||
--hide-project-names Obfuscate project names. When a project folder is
|
||||
detected instead of using the folder name as the
|
||||
project, a .wakatime-project file is created with a
|
||||
random project name.
|
||||
--exclude EXCLUDE Filename patterns to exclude from logging. POSIX regex
|
||||
syntax. Can be used more than once.
|
||||
--exclude-unknown-project
|
||||
|
|
|
@ -459,6 +459,46 @@ class ConfigsTestCase(TestCase):
|
|||
self.assertOfflineHeartbeatsSynced()
|
||||
self.assertSessionCacheSaved()
|
||||
|
||||
def test_obfuscte_project_names(self):
|
||||
self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].return_value = CustomResponse()
|
||||
|
||||
with TemporaryDirectory() as tempdir:
|
||||
shutil.copytree('tests/samples/projects/git', os.path.join(tempdir, 'git'))
|
||||
shutil.move(os.path.join(tempdir, 'git', 'dot_git'), os.path.join(tempdir, 'git', '.git'))
|
||||
entity = os.path.join(tempdir, 'git', 'emptyfile.txt')
|
||||
now = u(int(time.time()))
|
||||
config = 'tests/samples/configs/paranoid_projects.cfg'
|
||||
key = u(uuid.uuid4())
|
||||
dependencies = []
|
||||
generated_proj = 'Icy Bridge 42'
|
||||
|
||||
args = ['--file', entity, '--key', key, '--config', config, '--time', now, '--log-file', '~/.wakatime.log']
|
||||
|
||||
with mock.patch('wakatime.project.generate_project_name') as mock_proj:
|
||||
mock_proj.return_value = generated_proj
|
||||
|
||||
retval = execute(args)
|
||||
|
||||
self.assertEquals(retval, SUCCESS)
|
||||
self.assertNothingPrinted()
|
||||
|
||||
heartbeat = {
|
||||
'language': 'Text only',
|
||||
'lines': 0,
|
||||
'entity': os.path.realpath(entity),
|
||||
'project': generated_proj,
|
||||
'time': float(now),
|
||||
'is_write': False,
|
||||
'type': 'file',
|
||||
'dependencies': dependencies,
|
||||
'user_agent': ANY,
|
||||
}
|
||||
self.assertHeartbeatSent(heartbeat)
|
||||
|
||||
self.assertHeartbeatNotSavedOffline()
|
||||
self.assertOfflineHeartbeatsSynced()
|
||||
self.assertSessionCacheSaved()
|
||||
|
||||
@log_capture()
|
||||
def test_exclude_file(self, logs):
|
||||
logging.disable(logging.NOTSET)
|
||||
|
|
|
@ -15,6 +15,7 @@ from testfixtures import log_capture
|
|||
from wakatime.compat import u, open
|
||||
from wakatime.constants import API_ERROR, SUCCESS
|
||||
from wakatime.exceptions import NotYetImplemented
|
||||
from wakatime.project import generate_project_name
|
||||
from wakatime.projects.base import BaseProject
|
||||
from wakatime.projects.git import Git
|
||||
from .utils import ANY, DynamicIterable, TestCase, TemporaryDirectory, CustomResponse, mock, json
|
||||
|
@ -723,3 +724,7 @@ class ProjectTestCase(TestCase):
|
|||
self.assertNothingPrinted()
|
||||
self.assertNothingLogged(logs)
|
||||
self.assertEquals('proj-arg', self.patched['wakatime.offlinequeue.Queue.push'].call_args[0][0]['project'])
|
||||
|
||||
def test_generate_project_name(self):
|
||||
self.assertGreater(len(generate_project_name()), 1)
|
||||
self.assertNotEqual(generate_project_name(), generate_project_name())
|
||||
|
|
|
@ -133,6 +133,13 @@ def parse_arguments():
|
|||
parser.add_argument('--hidefilenames', dest='hidefilenames',
|
||||
action='store_true',
|
||||
help=argparse.SUPPRESS)
|
||||
parser.add_argument('--hide-project-names', dest='hide_project_names',
|
||||
action='store_true',
|
||||
help='Obfuscate project names. When a project ' +
|
||||
'folder is detected instead of using the ' +
|
||||
'folder name as the project, a ' +
|
||||
'.wakatime-project file is created with a ' +
|
||||
'random project name.')
|
||||
parser.add_argument('--exclude', dest='exclude', action='append',
|
||||
help='Filename patterns to exclude from logging. ' +
|
||||
'POSIX regex syntax. Can be used more than once.')
|
||||
|
@ -267,6 +274,8 @@ def parse_arguments():
|
|||
for pattern in option.split("\n"):
|
||||
if pattern.strip() != '':
|
||||
args.hide_filenames.append(pattern)
|
||||
if not args.hide_project_names and configs.has_option('settings', 'hide_project_names'):
|
||||
args.hide_project_names = configs.getboolean('settings', 'hide_project_names')
|
||||
if args.offline_deprecated:
|
||||
args.offline = False
|
||||
if args.offline and configs.has_option('settings', 'offline'):
|
||||
|
|
|
@ -9,8 +9,11 @@
|
|||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
import random
|
||||
|
||||
from .compat import open
|
||||
from .projects.git import Git
|
||||
from .projects.mercurial import Mercurial
|
||||
from .projects.projectfile import ProjectFile
|
||||
|
@ -62,7 +65,7 @@ def get_project_info(configs, heartbeat, data):
|
|||
branch_name = project.branch()
|
||||
break
|
||||
|
||||
if project_name is None:
|
||||
if project_name is None and not data.get('hide_project_names'):
|
||||
project_name = data.get('project') or heartbeat.args.project
|
||||
|
||||
if project_name is None or branch_name is None:
|
||||
|
@ -76,6 +79,15 @@ def get_project_info(configs, heartbeat, data):
|
|||
if project.process():
|
||||
project_name = project_name or project.name()
|
||||
branch_name = branch_name or project.branch()
|
||||
if data.get('hide_project_names'):
|
||||
branch_name = None
|
||||
project_name = generate_project_name()
|
||||
project_file = os.path.join(project.folder(), '.wakatime-project')
|
||||
try:
|
||||
with open(project_file, 'w') as fh:
|
||||
fh.write(project_name)
|
||||
except IOError:
|
||||
project_name = None
|
||||
break
|
||||
|
||||
if project_name is None:
|
||||
|
@ -88,3 +100,42 @@ def get_configs_for_plugin(plugin_name, configs):
|
|||
if configs and configs.has_section(plugin_name):
|
||||
return dict(configs.items(plugin_name))
|
||||
return None
|
||||
|
||||
|
||||
def generate_project_name():
|
||||
"""Generates a random project name."""
|
||||
|
||||
adjectives = [
|
||||
'aged', 'ancient', 'autumn', 'billowing', 'bitter', 'black', 'blue', 'bold',
|
||||
'broad', 'broken', 'calm', 'cold', 'cool', 'crimson', 'curly', 'damp',
|
||||
'dark', 'dawn', 'delicate', 'divine', 'dry', 'empty', 'falling', 'fancy',
|
||||
'flat', 'floral', 'fragrant', 'frosty', 'gentle', 'green', 'hidden', 'holy',
|
||||
'icy', 'jolly', 'late', 'lingering', 'little', 'lively', 'long', 'lucky',
|
||||
'misty', 'morning', 'muddy', 'mute', 'nameless', 'noisy', 'odd', 'old',
|
||||
'orange', 'patient', 'plain', 'polished', 'proud', 'purple', 'quiet', 'rapid',
|
||||
'raspy', 'red', 'restless', 'rough', 'round', 'royal', 'shiny', 'shrill',
|
||||
'shy', 'silent', 'small', 'snowy', 'soft', 'solitary', 'sparkling', 'spring',
|
||||
'square', 'steep', 'still', 'summer', 'super', 'sweet', 'throbbing', 'tight',
|
||||
'tiny', 'twilight', 'wandering', 'weathered', 'white', 'wild', 'winter', 'wispy',
|
||||
'withered', 'yellow', 'young'
|
||||
]
|
||||
nouns = [
|
||||
'art', 'band', 'bar', 'base', 'bird', 'block', 'boat', 'bonus',
|
||||
'bread', 'breeze', 'brook', 'bush', 'butterfly', 'cake', 'cell', 'cherry',
|
||||
'cloud', 'credit', 'darkness', 'dawn', 'dew', 'disk', 'dream', 'dust',
|
||||
'feather', 'field', 'fire', 'firefly', 'flower', 'fog', 'forest', 'frog',
|
||||
'frost', 'glade', 'glitter', 'grass', 'hall', 'hat', 'haze', 'heart',
|
||||
'hill', 'king', 'lab', 'lake', 'leaf', 'limit', 'math', 'meadow',
|
||||
'mode', 'moon', 'morning', 'mountain', 'mouse', 'mud', 'night', 'paper',
|
||||
'pine', 'poetry', 'pond', 'queen', 'rain', 'recipe', 'resonance', 'rice',
|
||||
'river', 'salad', 'scene', 'sea', 'shadow', 'shape', 'silence', 'sky',
|
||||
'smoke', 'snow', 'snowflake', 'sound', 'star', 'sun', 'sun', 'sunset',
|
||||
'surf', 'term', 'thunder', 'tooth', 'tree', 'truth', 'union', 'unit',
|
||||
'violet', 'voice', 'water', 'waterfall', 'wave', 'wildflower', 'wind', 'wood'
|
||||
]
|
||||
numbers = [str(x) for x in range(10)]
|
||||
return ' '.join([
|
||||
random.choice(adjectives).capitalize(),
|
||||
random.choice(nouns).capitalize(),
|
||||
random.choice(numbers) + random.choice(numbers),
|
||||
])
|
||||
|
|
|
@ -43,3 +43,8 @@ class BaseProject(object):
|
|||
""" Returns the current branch.
|
||||
"""
|
||||
raise NotYetImplemented()
|
||||
|
||||
def folder(self):
|
||||
""" Returns the project's top folder path.
|
||||
"""
|
||||
raise NotYetImplemented()
|
||||
|
|
|
@ -25,6 +25,7 @@ class Git(BaseProject):
|
|||
_submodule = None
|
||||
_project_name = None
|
||||
_head_file = None
|
||||
_project_folder = None
|
||||
|
||||
def process(self):
|
||||
return self._find_git_config_file(self.path)
|
||||
|
@ -40,6 +41,9 @@ class Git(BaseProject):
|
|||
return self._get_branch_from_head_file(line)
|
||||
return u('master')
|
||||
|
||||
def folder(self):
|
||||
return self._project_folder
|
||||
|
||||
def _find_git_config_file(self, path):
|
||||
path = os.path.realpath(path)
|
||||
if os.path.isfile(path):
|
||||
|
@ -47,6 +51,7 @@ class Git(BaseProject):
|
|||
if os.path.isfile(os.path.join(path, '.git', 'config')):
|
||||
self._project_name = os.path.basename(path)
|
||||
self._head_file = os.path.join(path, '.git', 'HEAD')
|
||||
self._project_folder = path
|
||||
return True
|
||||
|
||||
link_path = self._path_from_gitdir_link_file(path)
|
||||
|
@ -56,12 +61,14 @@ class Git(BaseProject):
|
|||
if self._is_worktree(link_path):
|
||||
self._project_name = self._project_from_worktree(link_path)
|
||||
self._head_file = os.path.join(link_path, 'HEAD')
|
||||
self._project_folder = path
|
||||
return True
|
||||
|
||||
# next check if this is a submodule
|
||||
if self._submodules_supported_for_path(path):
|
||||
self._project_name = os.path.basename(path)
|
||||
self._head_file = os.path.join(link_path, 'HEAD')
|
||||
self._project_folder = path
|
||||
return True
|
||||
|
||||
split_path = os.path.split(path)
|
||||
|
|
|
@ -47,6 +47,11 @@ class Mercurial(BaseProject):
|
|||
log.traceback(logging.WARNING)
|
||||
return u('default')
|
||||
|
||||
def folder(self):
|
||||
if self.configDir:
|
||||
return os.path.dirname(self.configDir)
|
||||
return None
|
||||
|
||||
def _find_hg_config_dir(self, path):
|
||||
path = os.path.realpath(path)
|
||||
if os.path.isfile(path):
|
||||
|
|
|
@ -41,6 +41,11 @@ class Subversion(BaseProject):
|
|||
return None # pragma: nocover
|
||||
return u(self.info['URL'].split('/')[-1].split('\\')[-1])
|
||||
|
||||
def folder(self):
|
||||
if 'Repository Root' not in self.info:
|
||||
return None
|
||||
return self.info['Repository Root']
|
||||
|
||||
def _find_binary(self):
|
||||
if self.binary_location:
|
||||
return self.binary_location
|
||||
|
|
Loading…
Reference in a new issue