| 1 | """Common utility functions |
| 2 | """ |
| 3 | |
| 4 | import errno, optparse, os, os.path, re, sys |
| 5 | from stgit.exception import * |
| 6 | from stgit.config import config |
| 7 | from stgit.out import * |
| 8 | |
| 9 | __copyright__ = """ |
| 10 | Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com> |
| 11 | |
| 12 | This program is free software; you can redistribute it and/or modify |
| 13 | it under the terms of the GNU General Public License version 2 as |
| 14 | published by the Free Software Foundation. |
| 15 | |
| 16 | This program is distributed in the hope that it will be useful, |
| 17 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 19 | GNU General Public License for more details. |
| 20 | |
| 21 | You should have received a copy of the GNU General Public License |
| 22 | along with this program; if not, write to the Free Software |
| 23 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| 24 | """ |
| 25 | |
| 26 | def mkdir_file(filename, mode): |
| 27 | """Opens filename with the given mode, creating the directory it's |
| 28 | in if it doesn't already exist.""" |
| 29 | create_dirs(os.path.dirname(filename)) |
| 30 | return file(filename, mode) |
| 31 | |
| 32 | def read_strings(filename): |
| 33 | """Reads the lines from a file |
| 34 | """ |
| 35 | f = file(filename, 'r') |
| 36 | lines = [line.strip() for line in f.readlines()] |
| 37 | f.close() |
| 38 | return lines |
| 39 | |
| 40 | def read_string(filename, multiline = False): |
| 41 | """Reads the first line from a file |
| 42 | """ |
| 43 | f = file(filename, 'r') |
| 44 | if multiline: |
| 45 | result = f.read() |
| 46 | else: |
| 47 | result = f.readline().strip() |
| 48 | f.close() |
| 49 | return result |
| 50 | |
| 51 | def write_strings(filename, lines): |
| 52 | """Write 'lines' sequence to file |
| 53 | """ |
| 54 | f = file(filename, 'w+') |
| 55 | f.writelines([line + '\n' for line in lines]) |
| 56 | f.close() |
| 57 | |
| 58 | def write_string(filename, line, multiline = False): |
| 59 | """Writes 'line' to file and truncates it |
| 60 | """ |
| 61 | f = mkdir_file(filename, 'w+') |
| 62 | if multiline: |
| 63 | f.write(line) |
| 64 | else: |
| 65 | print >> f, line |
| 66 | f.close() |
| 67 | |
| 68 | def append_strings(filename, lines): |
| 69 | """Appends 'lines' sequence to file |
| 70 | """ |
| 71 | f = mkdir_file(filename, 'a+') |
| 72 | for line in lines: |
| 73 | print >> f, line |
| 74 | f.close() |
| 75 | |
| 76 | def append_string(filename, line): |
| 77 | """Appends 'line' to file |
| 78 | """ |
| 79 | f = mkdir_file(filename, 'a+') |
| 80 | print >> f, line |
| 81 | f.close() |
| 82 | |
| 83 | def insert_string(filename, line): |
| 84 | """Inserts 'line' at the beginning of the file |
| 85 | """ |
| 86 | f = mkdir_file(filename, 'r+') |
| 87 | lines = f.readlines() |
| 88 | f.seek(0); f.truncate() |
| 89 | print >> f, line |
| 90 | f.writelines(lines) |
| 91 | f.close() |
| 92 | |
| 93 | def create_empty_file(name): |
| 94 | """Creates an empty file |
| 95 | """ |
| 96 | mkdir_file(name, 'w+').close() |
| 97 | |
| 98 | def list_files_and_dirs(path): |
| 99 | """Return the sets of filenames and directory names in a |
| 100 | directory.""" |
| 101 | files, dirs = [], [] |
| 102 | for fd in os.listdir(path): |
| 103 | full_fd = os.path.join(path, fd) |
| 104 | if os.path.isfile(full_fd): |
| 105 | files.append(fd) |
| 106 | elif os.path.isdir(full_fd): |
| 107 | dirs.append(fd) |
| 108 | return files, dirs |
| 109 | |
| 110 | def walk_tree(basedir): |
| 111 | """Starting in the given directory, iterate through all its |
| 112 | subdirectories. For each subdirectory, yield the name of the |
| 113 | subdirectory (relative to the base directory), the list of |
| 114 | filenames in the subdirectory, and the list of directory names in |
| 115 | the subdirectory.""" |
| 116 | subdirs = [''] |
| 117 | while subdirs: |
| 118 | subdir = subdirs.pop() |
| 119 | files, dirs = list_files_and_dirs(os.path.join(basedir, subdir)) |
| 120 | for d in dirs: |
| 121 | subdirs.append(os.path.join(subdir, d)) |
| 122 | yield subdir, files, dirs |
| 123 | |
| 124 | def strip_prefix(prefix, string): |
| 125 | """Return string, without the prefix. Blow up if string doesn't |
| 126 | start with prefix.""" |
| 127 | assert string.startswith(prefix) |
| 128 | return string[len(prefix):] |
| 129 | |
| 130 | def strip_suffix(suffix, string): |
| 131 | """Return string, without the suffix. Blow up if string doesn't |
| 132 | end with suffix.""" |
| 133 | assert string.endswith(suffix) |
| 134 | return string[:-len(suffix)] |
| 135 | |
| 136 | def remove_file_and_dirs(basedir, file): |
| 137 | """Remove join(basedir, file), and then remove the directory it |
| 138 | was in if empty, and try the same with its parent, until we find a |
| 139 | nonempty directory or reach basedir.""" |
| 140 | os.remove(os.path.join(basedir, file)) |
| 141 | try: |
| 142 | os.removedirs(os.path.join(basedir, os.path.dirname(file))) |
| 143 | except OSError: |
| 144 | # file's parent dir may not be empty after removal |
| 145 | pass |
| 146 | |
| 147 | def create_dirs(directory): |
| 148 | """Create the given directory, if the path doesn't already exist.""" |
| 149 | if directory and not os.path.isdir(directory): |
| 150 | create_dirs(os.path.dirname(directory)) |
| 151 | try: |
| 152 | os.mkdir(directory) |
| 153 | except OSError, e: |
| 154 | if e.errno != errno.EEXIST: |
| 155 | raise e |
| 156 | |
| 157 | def rename(basedir, file1, file2): |
| 158 | """Rename join(basedir, file1) to join(basedir, file2), not |
| 159 | leaving any empty directories behind and creating any directories |
| 160 | necessary.""" |
| 161 | full_file2 = os.path.join(basedir, file2) |
| 162 | create_dirs(os.path.dirname(full_file2)) |
| 163 | os.rename(os.path.join(basedir, file1), full_file2) |
| 164 | try: |
| 165 | os.removedirs(os.path.join(basedir, os.path.dirname(file1))) |
| 166 | except OSError: |
| 167 | # file1's parent dir may not be empty after move |
| 168 | pass |
| 169 | |
| 170 | class EditorException(StgException): |
| 171 | pass |
| 172 | |
| 173 | def call_editor(filename): |
| 174 | """Run the editor on the specified filename.""" |
| 175 | |
| 176 | # the editor |
| 177 | editor = config.get('stgit.editor') |
| 178 | if editor: |
| 179 | pass |
| 180 | elif 'EDITOR' in os.environ: |
| 181 | editor = os.environ['EDITOR'] |
| 182 | else: |
| 183 | editor = 'vi' |
| 184 | editor += ' %s' % filename |
| 185 | |
| 186 | out.start('Invoking the editor: "%s"' % editor) |
| 187 | err = os.system(editor) |
| 188 | if err: |
| 189 | raise EditorException, 'editor failed, exit code: %d' % err |
| 190 | out.done() |
| 191 | |
| 192 | def patch_name_from_msg(msg): |
| 193 | """Return a string to be used as a patch name. This is generated |
| 194 | from the top line of the string passed as argument.""" |
| 195 | if not msg: |
| 196 | return None |
| 197 | |
| 198 | name_len = config.get('stgit.namelength') |
| 199 | if not name_len: |
| 200 | name_len = 30 |
| 201 | |
| 202 | subject_line = msg.split('\n', 1)[0].lstrip().lower() |
| 203 | return re.sub('[\W]+', '-', subject_line).strip('-')[:name_len] |
| 204 | |
| 205 | def make_patch_name(msg, unacceptable, default_name = 'patch'): |
| 206 | """Return a patch name generated from the given commit message, |
| 207 | guaranteed to make unacceptable(name) be false. If the commit |
| 208 | message is empty, base the name on default_name instead.""" |
| 209 | patchname = patch_name_from_msg(msg) |
| 210 | if not patchname: |
| 211 | patchname = default_name |
| 212 | if unacceptable(patchname): |
| 213 | suffix = 0 |
| 214 | while unacceptable('%s-%d' % (patchname, suffix)): |
| 215 | suffix += 1 |
| 216 | patchname = '%s-%d' % (patchname, suffix) |
| 217 | return patchname |
| 218 | |
| 219 | # any and all functions are builtin in Python 2.5 and higher, but not |
| 220 | # in 2.4. |
| 221 | if not 'any' in dir(__builtins__): |
| 222 | def any(bools): |
| 223 | for b in bools: |
| 224 | if b: |
| 225 | return True |
| 226 | return False |
| 227 | if not 'all' in dir(__builtins__): |
| 228 | def all(bools): |
| 229 | for b in bools: |
| 230 | if not b: |
| 231 | return False |
| 232 | return True |
| 233 | |
| 234 | def make_sign_options(): |
| 235 | def callback(option, opt_str, value, parser, sign_str): |
| 236 | if parser.values.sign_str not in [None, sign_str]: |
| 237 | raise optparse.OptionValueError( |
| 238 | '--ack and --sign were both specified') |
| 239 | parser.values.sign_str = sign_str |
| 240 | return [optparse.make_option('--sign', action = 'callback', |
| 241 | callback = callback, dest = 'sign_str', |
| 242 | callback_args = ('Signed-off-by',), |
| 243 | help = 'add Signed-off-by line'), |
| 244 | optparse.make_option('--ack', action = 'callback', |
| 245 | callback = callback, dest = 'sign_str', |
| 246 | callback_args = ('Acked-by',), |
| 247 | help = 'add Acked-by line')] |
| 248 | |
| 249 | def add_sign_line(desc, sign_str, name, email): |
| 250 | if not sign_str: |
| 251 | return desc |
| 252 | sign_str = '%s: %s <%s>' % (sign_str, name, email) |
| 253 | if sign_str in desc: |
| 254 | return desc |
| 255 | desc = desc.rstrip() |
| 256 | if not any(s in desc for s in ['\nSigned-off-by:', '\nAcked-by:']): |
| 257 | desc = desc + '\n' |
| 258 | return '%s\n%s\n' % (desc, sign_str) |