Skip to content

Commit a5f90a0

Browse files
committed
import misc project from Bitbucket
Remove all the history: create Git repository from scratch.
0 parents  commit a5f90a0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+9187
-0
lines changed

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
unicode_book/build/
2+
unicode_book/surrogate_pair
3+
unicode_book/guess_encoding
4+
misc/locale
5+
antispam/private
6+
misc/libnobuffer.so
7+
*.py[cod]
8+
*.swp

bin/apply_patch.py

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
#!/usr/bin/env python
2+
"""
3+
Apply a patch.
4+
5+
Usage:
6+
7+
* File: "apply_patch.py fix.patch"
8+
* URL: "apply_patch.py http://example.com/fix.patch"
9+
* Reverse: "apply_patch.py -R fix.patch" or "apply_patch.py --reverse fix.patch"
10+
11+
Features:
12+
13+
* Automatically guess the "-p" option. For levels different than -p0, ask
14+
for confirmation.
15+
* If the patch doesn't match: ask for confirmation before changing files
16+
and creating .org and .rej files
17+
* The command line parameter can be an URL! http and https are supported
18+
using Python builtin urllib module.
19+
20+
The program supports Python 2 and Python 3.
21+
22+
TODO: handle correctly patch creating new files. Don't duplicate the content
23+
of a file.
24+
25+
The "patch" program is still required. The patch program must support
26+
--dry-run.
27+
28+
Distributed under the GNU GPL license version 3 or later.
29+
"""
30+
from __future__ import with_statement
31+
import os
32+
import subprocess
33+
import sys
34+
import tempfile
35+
try:
36+
from urllib.request import urlopen, Request
37+
except ImportError:
38+
from urllib2 import urlopen, Request
39+
40+
PY3 = (sys.version_info >= (3,))
41+
MAX_PATH_DIFF = 6
42+
IGNORE_DIRECTORIES = set(('.hg', '.git', 'build', '__pycache__'))
43+
PATCH_PROGRAM = 'patch'
44+
SEPARATORS = (
45+
'index ',
46+
'===========================================================',
47+
'diff ',
48+
)
49+
PREFIXES = ("index ", "new file mode ", "deleted file mode ")
50+
51+
def parse_filenames(filename):
52+
step = input_filename = output_filename = None
53+
with open(filename) as patch:
54+
line_number = 0
55+
for line in patch:
56+
line_number += 1
57+
line = line.rstrip()
58+
if step == 'input_file':
59+
if line.startswith("--- "):
60+
# split by space or tab
61+
input_filename = line[4:].split(None, 1)[0]
62+
step = 'output_file'
63+
elif any(line.startswith(prefix) for prefix in PREFIXES):
64+
# ignore "index 4bb3c01..9e84d63 100644"
65+
# ignore "new file mode 100644"
66+
# ignore "deleted file mode 100644"
67+
pass
68+
else:
69+
raise ValueError("Input filename line %s doesn't start with ---" % line_number)
70+
elif step == 'output_file':
71+
if not line.startswith("+++ "):
72+
raise ValueError("Output filename line doesn't start with +++: %r" % line)
73+
# split by space or tab
74+
output_filename = line[4:].split(None, 1)[0]
75+
yield (input_filename, output_filename)
76+
step = None
77+
else:
78+
if any(line.startswith(separator) for separator in SEPARATORS):
79+
step = 'input_file'
80+
81+
def strip_filename(filename, level):
82+
parts = filename.split(os.path.sep)
83+
parts = parts[level:]
84+
return os.path.sep.join(parts)
85+
86+
def _scanlevel(root, filenames):
87+
best = None
88+
for level in range(MAX_PATH_DIFF):
89+
failure = 0
90+
for in_fn, out_fn in filenames:
91+
if in_fn == '/dev/null':
92+
# new file: check the output directory
93+
filename = os.path.dirname(out_fn)
94+
else:
95+
if out_fn == '/dev/null':
96+
# deleted file
97+
filename = in_fn
98+
else:
99+
filename = out_fn
100+
filename = strip_filename(filename, level)
101+
if filename:
102+
filename = os.path.join(root, filename)
103+
if not os.path.exists(filename):
104+
failure += 1
105+
if not failure:
106+
return (level, False)
107+
if not best or best[1] > failure:
108+
best = (level, failure)
109+
if best[1] == len(filenames):
110+
return (None, True)
111+
else:
112+
print("Warning: scanning patch level tolerates %s/%s failures" % (best[1], len(filenames)))
113+
return (best[0], True)
114+
115+
def _search_directory(guess, parentdir, patch_filenames):
116+
found = False
117+
for name in os.listdir(parentdir):
118+
if name in ('.', '..'):
119+
continue
120+
if name in IGNORE_DIRECTORIES:
121+
continue
122+
fullname = os.path.join(parentdir, name)
123+
if not os.path.isdir(fullname):
124+
continue
125+
sys.stdout.write("."); sys.stdout.flush()
126+
if _search_directory(guess, fullname, patch_filenames):
127+
continue
128+
129+
level, error = _scanlevel(fullname, patch_filenames)
130+
if level is not None:
131+
guess.append((fullname, level))
132+
if not error:
133+
found = True
134+
return found
135+
136+
def search_directory(patch_filenames):
137+
stdout = sys.stdout
138+
stdout.write("Search for a match in subdirectories"); stdout.flush()
139+
guess = []
140+
_search_directory(guess, '.', patch_filenames)
141+
stdout.write("\n"); stdout.flush()
142+
return guess
143+
144+
def scanlevel(patch):
145+
filenames = list(parse_filenames(patch))
146+
if not filenames:
147+
print("Error: unable to parse filenames")
148+
sys.exit(1)
149+
150+
level, error = _scanlevel('.', filenames)
151+
if level is not None:
152+
return level
153+
154+
print("Unable to find patch level of the following filenames:")
155+
for in_fn, out_fn in filenames:
156+
print("> %s" % out_fn)
157+
print("")
158+
guess = search_directory(filenames)
159+
if len(guess) == 1:
160+
root, level = guess[0]
161+
print("Patch looks to fit in directory %s (level %s)" % (root, level))
162+
ask_confirmation("Apply patch from directory %s (y/N)?" % root)
163+
os.chdir(root)
164+
return level
165+
elif guess:
166+
print("You may try from directories:")
167+
for root, level in guess:
168+
print("%s (level %s)" % (root, level))
169+
else:
170+
print("Sorry, no matching subdirectory.")
171+
sys.exit(1)
172+
173+
def ask_confirmation(prompt):
174+
try:
175+
if PY3:
176+
answer = input(prompt)
177+
else:
178+
answer = raw_input(prompt)
179+
except (KeyboardInterrupt, EOFError):
180+
print("no")
181+
sys.exit(1)
182+
answer = answer.lower().strip()
183+
if answer != 'y':
184+
sys.exit(1)
185+
186+
def downloadPatch(url, filename):
187+
request = Request(url)
188+
url = urlopen(request)
189+
data = url.read()
190+
fp = open(filename, "wb")
191+
fp.write(data)
192+
fp.close()
193+
194+
def usage():
195+
print("usage: %s [-R|--reverse] patch" % sys.argv[0])
196+
print("patch can be a file name or an URL.")
197+
198+
def main():
199+
tmpfile = None
200+
reverse = False
201+
if len(sys.argv) == 2:
202+
filename = sys.argv[1]
203+
elif len(sys.argv) == 3:
204+
if sys.argv[1] not in ('-R', '--reverse'):
205+
usage()
206+
sys.exit(1)
207+
reverse = True
208+
filename = sys.argv[2]
209+
else:
210+
usage()
211+
sys.exit(1)
212+
try:
213+
if filename.startswith(('http://', 'https://')):
214+
tmpfile = tempfile.NamedTemporaryFile()
215+
downloadPatch(filename, tmpfile.name)
216+
filename = tmpfile.name
217+
218+
filename = os.path.realpath(filename)
219+
if not os.path.exists(filename):
220+
print("ERROR: Patch %s doesn't exist." % filename)
221+
sys.exit(1)
222+
level = scanlevel(filename)
223+
print("Patch level: %s" % level)
224+
225+
# --dry-run: don't change any files
226+
# --batch: suppress questions
227+
command = [PATCH_PROGRAM, '--dry-run', '--batch']
228+
if reverse:
229+
# Revert a patch
230+
command.append('--reverse')
231+
else:
232+
# ask to not try to apply the patch backward
233+
command.append('--forward')
234+
command.extend(['-p%s' % level, '-i', filename])
235+
try:
236+
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
237+
except OSError as err:
238+
print("Fail to run %s: %s" % (' '.join(command), err))
239+
sys.exit(1)
240+
stdout, stderr = process.communicate()
241+
returncode = process.wait()
242+
243+
if returncode:
244+
if PY3:
245+
sys.stdout.flush()
246+
sys.stdout.buffer.write(stdout)
247+
sys.stdout.buffer.flush()
248+
else:
249+
sys.stdout.write(stdout)
250+
print
251+
ask_confirmation("Dry run failed. Apply anyway (y/N)?")
252+
253+
command = [PATCH_PROGRAM, '--forward']
254+
if reverse:
255+
command.append('-R')
256+
command.extend(['-p%s' % level, '-i', filename])
257+
process = subprocess.Popen(command)
258+
returncode = process.wait()
259+
finally:
260+
if tmpfile is not None:
261+
tmpfile.close()
262+
if returncode:
263+
print("Patch failed.")
264+
else:
265+
print("Patch applied correctly.")
266+
sys.exit(returncode)
267+
268+
269+
main()

0 commit comments

Comments
 (0)