1 """Common utility functions"""
9 from stgit
.compat
import environ_get
10 from stgit
.config
import config
11 from stgit
.exception
import StgException
12 from stgit
.out
import out
13 from stgit
.run
import Run
16 Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com>
18 This program is free software; you can redistribute it and/or modify
19 it under the terms of the GNU General Public License version 2 as
20 published by the Free Software Foundation.
22 This program is distributed in the hope that it will be useful,
23 but WITHOUT ANY WARRANTY; without even the implied warranty of
24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 GNU General Public License for more details.
27 You should have received a copy of the GNU General Public License
28 along with this program; if not, see http://www.gnu.org/licenses/.
32 def mkdir_file(filename
, mode
, encoding
='utf-8'):
33 """Opens filename with the given mode, creating the directory it's
34 in if it doesn't already exist."""
35 create_dirs(os
.path
.dirname(filename
))
36 return open(filename
, mode
, encoding
=encoding
)
39 def read_strings(filename
, encoding
='utf-8'):
40 """Reads the lines from a file"""
41 with
open(filename
, encoding
=encoding
) as f
:
42 return [line
.strip() for line
in f
.readlines()]
45 def read_string(filename
, encoding
='utf-8'):
46 """Reads the first line from a file"""
47 with
open(filename
, encoding
=encoding
) as f
:
48 return f
.readline().strip()
51 def write_strings(filename
, lines
, encoding
='utf-8'):
52 """Write 'lines' sequence to file"""
53 with
open(filename
, 'w+', encoding
=encoding
) as f
:
58 def write_string(filename
, line
, multiline
=False, encoding
='utf-8'):
59 """Writes 'line' to file and truncates it"""
60 with
mkdir_file(filename
, 'w+', encoding
) as f
:
61 print(line
, end
='' if multiline
else '\n', file=f
)
64 def create_empty_file(name
):
65 """Creates an empty file"""
66 mkdir_file(name
, 'w+').close()
69 def strip_prefix(prefix
, string
):
70 """Return string, without the prefix. Blow up if string doesn't
72 assert string
.startswith(prefix
)
73 return string
[len(prefix
) :]
76 def create_dirs(directory
):
77 """Create the given directory, if the path doesn't already exist."""
78 if directory
and not os
.path
.isdir(directory
):
79 create_dirs(os
.path
.dirname(directory
))
83 if e
.errno
!= errno
.EEXIST
:
87 def rename(basedir
, file1
, file2
):
88 """Rename join(basedir, file1) to join(basedir, file2), not
89 leaving any empty directories behind and creating any directories
91 full_file2
= os
.path
.join(basedir
, file2
)
92 create_dirs(os
.path
.dirname(full_file2
))
93 os
.rename(os
.path
.join(basedir
, file1
), full_file2
)
95 os
.removedirs(os
.path
.join(basedir
, os
.path
.dirname(file1
)))
97 # file1's parent dir may not be empty after move
101 class EditorException(StgException
):
107 environ_get('GIT_EDITOR'),
108 config
.get('stgit.editor'), # legacy
109 config
.get('core.editor'),
110 environ_get('VISUAL'),
111 environ_get('EDITOR'),
118 def call_editor(filename
):
119 """Run the editor on the specified filename."""
120 cmd
= '%s %s' % (get_editor(), filename
)
121 out
.start('Invoking the editor: "%s"' % cmd
)
124 raise EditorException('editor failed, exit code: %d' % err
)
128 def get_hook(repository
, hook_name
, extra_env
={}):
129 hook_path
= os
.path
.join(repository
.directory
, 'hooks', hook_name
)
130 if not (os
.path
.isfile(hook_path
) and os
.access(hook_path
, os
.X_OK
)):
133 default_iw
= repository
.default_iw
134 prefix_dir
= os
.path
.relpath(os
.getcwd(), default_iw
.cwd
)
135 if prefix_dir
== os
.curdir
:
138 prefix
= os
.path
.join(prefix_dir
, '')
139 extra_env
= add_dict(extra_env
, {'GIT_PREFIX': prefix
})
141 def hook(*parameters
):
143 argv
.extend(parameters
)
145 # On Windows, run the hook using "bash" explicitly
146 if os
.name
!= 'posix':
147 argv
.insert(0, 'bash')
149 default_iw
.run(argv
, extra_env
).run()
151 hook
.__name
__ = str(hook_name
)
155 def run_hook_on_bytes(hook
, byte_data
, *args
):
156 temp
= tempfile
.NamedTemporaryFile('wb', prefix
='stgit-hook', delete
=False)
159 temp
.write(byte_data
)
161 with
open(temp
.name
, 'rb') as data_file
:
162 return data_file
.read()
167 def edit_string(s
, filename
, encoding
='utf-8'):
168 with
open(filename
, 'w', encoding
=encoding
) as f
:
170 call_editor(filename
)
171 with
open(filename
, encoding
=encoding
) as f
:
177 def edit_bytes(s
, filename
):
178 with
open(filename
, 'wb') as f
:
180 call_editor(filename
)
181 with
open(filename
, 'rb') as f
:
187 def find_patch_name(patchname
, unacceptable
):
188 """Find a patch name which is acceptable."""
189 if unacceptable(patchname
):
191 while unacceptable('%s-%d' % (patchname
, suffix
)):
193 patchname
= '%s-%d' % (patchname
, suffix
)
197 def patch_name_from_msg(msg
):
198 """Return a string to be used as a patch name. This is generated
199 from the top line of the string passed as argument."""
203 name_len
= config
.getint('stgit.namelength')
207 subject_line
= msg
.split('\n', 1)[0].lstrip().lower()
208 words
= re
.sub(r
'(?u)[\W]+', ' ', subject_line
).split()
210 # use loop to avoid truncating the last name
211 name
= words
and words
[0] or 'unknown'
212 for word
in words
[1:]:
213 new
= name
+ '-' + word
214 if len(new
) > name_len
:
221 def make_patch_name(msg
, unacceptable
, default_name
='patch'):
222 """Return a patch name generated from the given commit message,
223 guaranteed to make unacceptable(name) be false. If the commit
224 message is empty, base the name on default_name instead."""
225 patchname
= patch_name_from_msg(msg
)
227 patchname
= default_name
228 return find_patch_name(patchname
, unacceptable
)
231 def add_trailer(message
, trailer
, name
, email
):
232 trailer_line
= '%s: %s <%s>' % (trailer
, name
, email
)
234 Run('git', 'interpret-trailers', '--trailer', trailer_line
)
240 def parse_name_email(address
):
241 """Return a tuple consisting of the name and email parsed from a
242 standard 'name <email>' or 'email (name)' string."""
243 address
= re
.sub(r
'[\\"]', r
'\\\g<0>', address
)
244 str_list
= re
.findall(r
'^(.*)\s*<(.*)>\s*$', address
)
246 str_list
= re
.findall(r
'^(.*)\s*\((.*)\)\s*$', address
)
249 return (str_list
[0][1], str_list
[0][0])
254 STGIT_SUCCESS
= 0 # everything's OK
255 STGIT_GENERAL_ERROR
= 1 # seems to be non-command-specific error
256 STGIT_COMMAND_ERROR
= 2 # seems to be a command that failed
257 STGIT_CONFLICT
= 3 # merge conflict, otherwise OK
258 STGIT_BUG_ERROR
= 4 # a bug in StGit
261 def add_dict(d1
, d2
):
262 """Return a new dict with the contents of both d1 and d2. In case of
263 conflicting mappings, d2 takes precedence."""