Use README.md in setup.py long_description
[stgit.git] / stgit / utils.py
blob883bf3f49dc53d3550c16a448ee7774581d801fd
1 """Common utility functions"""
3 import os
4 import re
5 import shutil
6 import sys
7 import tempfile
8 from io import open
10 from stgit.compat import environ_get
11 from stgit.config import config
12 from stgit.exception import StgException
13 from stgit.out import out
14 from stgit.run import Run
16 __copyright__ = """
17 Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com>
19 This program is free software; you can redistribute it and/or modify
20 it under the terms of the GNU General Public License version 2 as
21 published by the Free Software Foundation.
23 This program is distributed in the hope that it will be useful,
24 but WITHOUT ANY WARRANTY; without even the implied warranty of
25 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 GNU General Public License for more details.
28 You should have received a copy of the GNU General Public License
29 along with this program; if not, see http://www.gnu.org/licenses/.
30 """
33 def strip_prefix(prefix, string):
34 """Return string, without the specified prefix.
36 The string must start with the prefix.
38 """
39 assert string.startswith(prefix)
40 return string[len(prefix) :]
43 class EditorException(StgException):
44 pass
47 def get_editor():
48 for editor in [
49 environ_get('GIT_EDITOR'),
50 config.get('stgit.editor'), # legacy
51 config.get('core.editor'),
52 environ_get('VISUAL'),
53 environ_get('EDITOR'),
54 'vi',
56 if editor:
57 return editor
60 def call_editor(filename):
61 """Run the editor on the specified filename."""
62 cmd = '%s %s' % (get_editor(), filename)
63 out.start('Invoking the editor: "%s"' % cmd)
64 err = os.system(cmd)
65 if err:
66 raise EditorException('editor failed, exit code: %d' % err)
67 out.done()
70 def get_hooks_path(repository):
71 hooks_path = config.get('core.hookspath')
72 if hooks_path is None:
73 return os.path.join(repository.directory, 'hooks')
74 elif os.path.isabs(hooks_path):
75 return hooks_path
76 else:
77 return os.path.join(repository.default_worktree.directory, hooks_path)
80 def get_hook(repository, hook_name, extra_env=None):
81 if not extra_env:
82 extra_env = {}
83 hook_path = os.path.join(get_hooks_path(repository), hook_name)
84 if not (os.path.isfile(hook_path) and os.access(hook_path, os.X_OK)):
85 return None
87 prefix_dir = os.path.relpath(os.getcwd(), repository.default_worktree.directory)
88 if prefix_dir == os.curdir:
89 prefix = ''
90 else:
91 prefix = os.path.join(prefix_dir, '')
92 extra_env = add_dict(extra_env, {'GIT_PREFIX': prefix})
94 def hook(*parameters):
95 if sys.platform == 'win32':
96 # On Windows, run the hook using "bash" explicitly.
97 # Try to locate bash.exe in user's PATH, but avoid the WSL
98 # shim/bootstrapper %SYSTEMROOT%/System32/bash.exe
99 systemroot = os.environ.get('SYSTEMROOT')
100 if systemroot:
101 system32 = os.path.normcase(os.path.join(systemroot, 'system32'))
102 path = os.pathsep.join(
104 for p in os.environ.get('PATH', '').split(os.pathsep)
105 if os.path.normcase(p) != system32
107 else:
108 path = None
110 # Find bash with user's path (sans System32).
111 bash_exe = shutil.which('bash.exe', path=path)
112 if not bash_exe:
113 # Next try finding the bash.exe that came with Git for Windows.
114 git_exe = shutil.which('git.exe', path=path)
115 if not git_exe:
116 raise StgException('Failed to locate either bash.exe or git.exe')
117 bash_exe = os.path.join(
118 os.path.dirname(os.path.dirname(git_exe)),
119 'bin',
120 'bash.exe',
123 argv = [bash_exe, hook_path]
124 else:
125 argv = [hook_path]
127 argv.extend(parameters)
129 repository.default_iw.run(argv, extra_env).run()
131 hook.__name__ = hook_name
132 return hook
135 def run_hook_on_bytes(hook, byte_data, *args):
136 temp = tempfile.NamedTemporaryFile('wb', prefix='stgit-hook', delete=False)
137 try:
138 with temp:
139 temp.write(byte_data)
140 hook(temp.name)
141 with open(temp.name, 'rb') as data_file:
142 return data_file.read()
143 finally:
144 os.unlink(temp.name)
147 def edit_string(s, filename, encoding='utf-8'):
148 with open(filename, 'w', encoding=encoding) as f:
149 f.write(s)
150 call_editor(filename)
151 with open(filename, encoding=encoding) as f:
152 s = f.read()
153 os.remove(filename)
154 return s
157 def edit_bytes(s, filename):
158 with open(filename, 'wb') as f:
159 f.write(s)
160 call_editor(filename)
161 with open(filename, 'rb') as f:
162 s = f.read()
163 os.remove(filename)
164 return s
167 def add_trailer(message, trailer, name, email):
168 trailer_line = '%s: %s <%s>' % (trailer, name, email)
169 return (
170 Run('git', 'interpret-trailers', '--trailer', trailer_line)
171 .raw_input(message)
172 .raw_output()
176 def parse_name_email(address):
177 """Parse an email address string.
179 Returns a tuple consisting of the name and email parsed from a
180 standard 'name <email>' or 'email (name)' string.
183 address = re.sub(r'[\\"]', r'\\\g<0>', address)
184 str_list = re.findall(r'^(.*)\s*<(.*)>\s*$', address)
185 if not str_list:
186 str_list = re.findall(r'^(.*)\s*\((.*)\)\s*$', address)
187 if not str_list:
188 return None
189 return (str_list[0][1], str_list[0][0])
190 return str_list[0]
193 # Exit codes.
194 STGIT_SUCCESS = 0 # everything's OK
195 STGIT_GENERAL_ERROR = 1 # seems to be non-command-specific error
196 STGIT_COMMAND_ERROR = 2 # seems to be a command that failed
197 STGIT_CONFLICT = 3 # merge conflict, otherwise OK
198 STGIT_BUG_ERROR = 4 # a bug in StGit
201 def add_dict(d1, d2):
202 """Return a new dict with the contents of both d1 and d2.
204 In case of conflicting mappings, d2 takes precedence.
207 d = dict(d1)
208 d.update(d2)
209 return d