sapling/eden/fs/cli/util.py
Michael Bolin 634e96872e Add initial support for hooks akin to Git hooks for Eden.
Summary:
This design is inspired by that of Git hooks:
https://git-scm.com/docs/githooks

By default, `/etc/eden/hooks` should be the place where Eden looks for
hooks; however, this can be overridden in `~/.edenrc` on a per-`repository` basis.
This directory should be installed as part of installing Eden.
There is information in `eden/hooks/README.md` about this.

The first hook that is supported is for post-clone logic for a repository.

This change demonstrates the need for an `eden config --get <value>`
analogous to what Git has, as hooks should be able to leverage this in their
own scripts. There introduces a `TODO` in `post-clone.py` where such a
feature would be useful, so that I could add the following to my `~/.edenrc`
to develop the Eden extension for Hg:

```
[hooks]
hg.edenextension = /data/users/mbolin/fbsource/fbcode/eden/hg/eden

[repository fbsource]
path = /data/users/mbolin/fbsource
type = hg
hooks = /data/users/mbolin/eden-hooks
```

Note that this revision also introduces a `generate-hooks-dir` script that can be
used to generate the standard `/etc/eden/hooks` directory that we intend to
distribute with Eden. This is also useful in creating the basis for a custom `hooks`
directory that can be specified as shown above in an `~/.edenrc` file.

Reviewed By: simpkins

Differential Revision: D3858635

fbshipit-source-id: 215ca26379a4b3b0a07d50845fd645b4d9ccf0f2
2016-09-26 13:53:05 -07:00

144 lines
3.9 KiB
Python

#!/usr/bin/env python3
#
# Copyright (c) 2016, Facebook, Inc.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree. An additional grant
# of patent rights can be found in the PATENTS file in the same directory.
import errno
import os
import pwd
import subprocess
import time
class TimeoutError(Exception):
pass
def poll_until(function, timeout, interval=0.2, timeout_ex=None):
'''
Call the specified function repeatedly until it returns non-None.
Returns the function result.
Sleep 'interval' seconds between calls. If 'timeout' seconds passes
before the function returns a non-None result, raise an exception.
If a 'timeout_ex' argument is supplied, that exception object is
raised, otherwise a TimeoutError is raised.
'''
end_time = time.time() + timeout
while True:
result = function()
if result is not None:
return result
if time.time() >= end_time:
if timeout_ex is not None:
raise timeout_ex
raise TimeoutError('timed out waiting on function {}'.format(
function.__name__))
time.sleep(interval)
def get_home_dir():
home_dir = None
if os.name == 'nt':
home_dir = os.getenv('USERPROFILE')
else:
home_dir = os.getenv('HOME')
if not home_dir:
home_dir = pwd.getpwuid(os.getuid()).pw_dir
return home_dir
def mkdir_p(path):
'''Performs `mkdir -p <path>` and returns the path.'''
try:
os.makedirs(path)
except OSError as e:
if e.errno != errno.EEXIST:
raise
return path
def is_git_dir(path):
return (os.path.isdir(os.path.join(path, 'objects')) and
os.path.isdir(os.path.join(path, 'refs')) and
os.path.exists(os.path.join(path, 'HEAD')))
def get_git_dir(path):
'''
If path points to a git repository, return the path to the repository .git
directory. Otherwise, if the path is not a git repository, return None.
'''
path = os.path.realpath(path)
if path.endswith('.git') and is_git_dir(path):
return path
git_subdir = os.path.join(path, '.git')
if is_git_dir(git_subdir):
return git_subdir
return None
def get_git_commit(git_dir):
cmd = ['git', 'rev-parse', 'HEAD']
out = subprocess.check_output(cmd, cwd=git_dir)
return out.strip().decode('utf-8', errors='surrogateescape')
def get_hg_repo(path):
'''
If path points to a mercurial repository, return a normalized path to the
repository root. Otherwise, if path is not a mercurial repository, return
None.
'''
repo_path = os.path.realpath(path)
hg_dir = os.path.join(repo_path, '.hg')
if not os.path.isdir(hg_dir):
return None
# Check to see if this is a shared working directory from another
# repository
try:
with open(os.path.join(hg_dir, 'sharedpath'), 'r') as f:
hg_dir = f.readline().rstrip('\n')
hg_dir = os.path.realpath(hg_dir)
repo_path = os.path.dirname(hg_dir)
except EnvironmentError as ex:
if ex.errno != errno.ENOENT:
raise
if not os.path.isdir(os.path.join(hg_dir, 'store')):
return None
return repo_path
def get_hg_commit(repo):
env = os.environ.copy()
env['HGPLAIN'] = '1'
cmd = ['hg', '--cwd', repo, 'log', '-T{node}\\n', '-r.']
out = subprocess.check_output(cmd, env=env)
return out.strip().decode('utf-8', errors='surrogateescape')
def get_repo_source_and_type(path):
repo_source = ''
repo_type = None
git_dir = get_git_dir(path)
if git_dir:
repo_source = git_dir
repo_type = 'git'
else:
hg_repo = get_hg_repo(path)
if hg_repo:
repo_source = hg_repo
repo_type = 'hg'
return (repo_source, repo_type)