226 if not gitpatches: |
226 if not gitpatches: |
227 dopatch = GP_PATCH |
227 dopatch = GP_PATCH |
228 |
228 |
229 return (dopatch, gitpatches) |
229 return (dopatch, gitpatches) |
230 |
230 |
|
231 class linereader: |
|
232 # simple class to allow pushing lines back into the input stream |
|
233 def __init__(self, fp, textmode=False): |
|
234 self.fp = fp |
|
235 self.buf = [] |
|
236 self.textmode = textmode |
|
237 |
|
238 def push(self, line): |
|
239 if line is not None: |
|
240 self.buf.append(line) |
|
241 |
|
242 def readline(self): |
|
243 if self.buf: |
|
244 l = self.buf[0] |
|
245 del self.buf[0] |
|
246 return l |
|
247 l = self.fp.readline() |
|
248 if self.textmode and l.endswith('\r\n'): |
|
249 l = l[:-2] + '\n' |
|
250 return l |
|
251 |
|
252 def __iter__(self): |
|
253 while 1: |
|
254 l = self.readline() |
|
255 if not l: |
|
256 break |
|
257 yield l |
|
258 |
231 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1 |
259 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1 |
232 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@') |
260 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@') |
233 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)') |
261 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)') |
234 |
262 |
235 class patchfile(object): |
263 class patchfile(object): |
236 def __init__(self, ui, fname, opener, missing=False): |
264 def __init__(self, ui, fname, opener, missing=False, eol=None): |
237 self.fname = fname |
265 self.fname = fname |
|
266 self.eol = eol |
238 self.opener = opener |
267 self.opener = opener |
239 self.ui = ui |
268 self.ui = ui |
240 self.lines = [] |
269 self.lines = [] |
241 self.exists = False |
270 self.exists = False |
242 self.missing = missing |
271 self.missing = missing |
258 self.hunks = 0 |
287 self.hunks = 0 |
259 |
288 |
260 def readlines(self, fname): |
289 def readlines(self, fname): |
261 fp = self.opener(fname, 'r') |
290 fp = self.opener(fname, 'r') |
262 try: |
291 try: |
263 return fp.readlines() |
292 return list(linereader(fp, self.eol is not None)) |
264 finally: |
293 finally: |
265 fp.close() |
294 fp.close() |
266 |
295 |
267 def writelines(self, fname, lines): |
296 def writelines(self, fname, lines): |
268 fp = self.opener(fname, 'w') |
297 fp = self.opener(fname, 'w') |
269 try: |
298 try: |
270 fp.writelines(lines) |
299 if self.eol and self.eol != '\n': |
|
300 for l in lines: |
|
301 if l and l[-1] == '\n': |
|
302 l = l[:1] + self.eol |
|
303 fp.write(l) |
|
304 else: |
|
305 fp.writelines(lines) |
271 finally: |
306 finally: |
272 fp.close() |
307 fp.close() |
273 |
308 |
274 def unlink(self, fname): |
309 def unlink(self, fname): |
275 os.unlink(fname) |
310 os.unlink(fname) |
780 else: |
815 else: |
781 raise PatchError(_("undefined source and destination files")) |
816 raise PatchError(_("undefined source and destination files")) |
782 |
817 |
783 return fname, missing |
818 return fname, missing |
784 |
819 |
785 class linereader(object): |
|
786 # simple class to allow pushing lines back into the input stream |
|
787 def __init__(self, fp): |
|
788 self.fp = fp |
|
789 self.buf = [] |
|
790 |
|
791 def push(self, line): |
|
792 if line is not None: |
|
793 self.buf.append(line) |
|
794 |
|
795 def readline(self): |
|
796 if self.buf: |
|
797 return self.buf.pop(0) |
|
798 return self.fp.readline() |
|
799 |
|
800 def __iter__(self): |
|
801 while 1: |
|
802 l = self.readline() |
|
803 if not l: |
|
804 break |
|
805 yield l |
|
806 |
|
807 def scangitpatch(lr, firstline): |
820 def scangitpatch(lr, firstline): |
808 """ |
821 """ |
809 Git patches can emit: |
822 Git patches can emit: |
810 - rename a to b |
823 - rename a to b |
811 - change b |
824 - change b |
822 try: |
835 try: |
823 pos = lr.fp.tell() |
836 pos = lr.fp.tell() |
824 fp = lr.fp |
837 fp = lr.fp |
825 except IOError: |
838 except IOError: |
826 fp = cStringIO.StringIO(lr.fp.read()) |
839 fp = cStringIO.StringIO(lr.fp.read()) |
827 gitlr = linereader(fp) |
840 gitlr = linereader(fp, lr.textmode) |
828 gitlr.push(firstline) |
841 gitlr.push(firstline) |
829 (dopatch, gitpatches) = readgitpatch(gitlr) |
842 (dopatch, gitpatches) = readgitpatch(gitlr) |
830 fp.seek(pos) |
843 fp.seek(pos) |
831 return dopatch, gitpatches |
844 return dopatch, gitpatches |
832 |
845 |
833 def iterhunks(ui, fp, sourcefile=None): |
846 def iterhunks(ui, fp, sourcefile=None, textmode=False): |
834 """Read a patch and yield the following events: |
847 """Read a patch and yield the following events: |
835 - ("file", afile, bfile, firsthunk): select a new target file. |
848 - ("file", afile, bfile, firsthunk): select a new target file. |
836 - ("hunk", hunk): a new hunk is ready to be applied, follows a |
849 - ("hunk", hunk): a new hunk is ready to be applied, follows a |
837 "file" event. |
850 "file" event. |
838 - ("git", gitchanges): current diff is in git format, gitchanges |
851 - ("git", gitchanges): current diff is in git format, gitchanges |
839 maps filenames to gitpatch records. Unique event. |
852 maps filenames to gitpatch records. Unique event. |
|
853 |
|
854 If textmode is True, input line-endings are normalized to LF. |
840 """ |
855 """ |
841 changed = {} |
856 changed = {} |
842 current_hunk = None |
857 current_hunk = None |
843 afile = "" |
858 afile = "" |
844 bfile = "" |
859 bfile = "" |
952 current_hunk.desc)) |
967 current_hunk.desc)) |
953 |
968 |
954 if hunknum == 0 and dopatch and not gitworkdone: |
969 if hunknum == 0 and dopatch and not gitworkdone: |
955 raise NoHunks |
970 raise NoHunks |
956 |
971 |
957 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False): |
972 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False, |
958 """reads a patch from fp and tries to apply it. The dict 'changed' is |
973 eol=None): |
959 filled in with all of the filenames changed by the patch. Returns 0 |
974 """ |
960 for a clean patch, -1 if any rejects were found and 1 if there was |
975 Reads a patch from fp and tries to apply it. |
961 any fuzz.""" |
976 |
962 |
977 The dict 'changed' is filled in with all of the filenames changed |
|
978 by the patch. Returns 0 for a clean patch, -1 if any rejects were |
|
979 found and 1 if there was any fuzz. |
|
980 |
|
981 If 'eol' is None, the patch content and patched file are read in |
|
982 binary mode. Otherwise, line endings are ignored when patching then |
|
983 normalized to 'eol' (usually '\n' or \r\n'). |
|
984 """ |
963 rejects = 0 |
985 rejects = 0 |
964 err = 0 |
986 err = 0 |
965 current_file = None |
987 current_file = None |
966 gitpatches = None |
988 gitpatches = None |
967 opener = util.opener(os.getcwd()) |
989 opener = util.opener(os.getcwd()) |
|
990 textmode = eol is not None |
968 |
991 |
969 def closefile(): |
992 def closefile(): |
970 if not current_file: |
993 if not current_file: |
971 return 0 |
994 return 0 |
972 current_file.close() |
995 current_file.close() |
973 return len(current_file.rej) |
996 return len(current_file.rej) |
974 |
997 |
975 for state, values in iterhunks(ui, fp, sourcefile): |
998 for state, values in iterhunks(ui, fp, sourcefile, textmode): |
976 if state == 'hunk': |
999 if state == 'hunk': |
977 if not current_file: |
1000 if not current_file: |
978 continue |
1001 continue |
979 current_hunk = values |
1002 current_hunk = values |
980 ret = current_file.apply(current_hunk, reverse) |
1003 ret = current_file.apply(current_hunk, reverse) |
985 elif state == 'file': |
1008 elif state == 'file': |
986 rejects += closefile() |
1009 rejects += closefile() |
987 afile, bfile, first_hunk = values |
1010 afile, bfile, first_hunk = values |
988 try: |
1011 try: |
989 if sourcefile: |
1012 if sourcefile: |
990 current_file = patchfile(ui, sourcefile, opener) |
1013 current_file = patchfile(ui, sourcefile, opener, eol=eol) |
991 else: |
1014 else: |
992 current_file, missing = selectfile(afile, bfile, first_hunk, |
1015 current_file, missing = selectfile(afile, bfile, first_hunk, |
993 strip, reverse) |
1016 strip, reverse) |
994 current_file = patchfile(ui, current_file, opener, missing) |
1017 current_file = patchfile(ui, current_file, opener, missing, eol) |
995 except PatchError, err: |
1018 except PatchError, err: |
996 ui.warn(str(err) + '\n') |
1019 ui.warn(str(err) + '\n') |
997 current_file, current_hunk = None, None |
1020 current_file, current_hunk = None, None |
998 rejects += 1 |
1021 rejects += 1 |
999 continue |
1022 continue |
1102 if code: |
1125 if code: |
1103 raise PatchError(_("patch command failed: %s") % |
1126 raise PatchError(_("patch command failed: %s") % |
1104 util.explain_exit(code)[0]) |
1127 util.explain_exit(code)[0]) |
1105 return fuzz |
1128 return fuzz |
1106 |
1129 |
1107 def internalpatch(patchobj, ui, strip, cwd, files={}): |
1130 def internalpatch(patchobj, ui, strip, cwd, files={}, eolmode='strict'): |
1108 """use builtin patch to apply <patchobj> to the working directory. |
1131 """use builtin patch to apply <patchobj> to the working directory. |
1109 returns whether patch was applied with fuzz factor.""" |
1132 returns whether patch was applied with fuzz factor.""" |
|
1133 |
|
1134 if eolmode is None: |
|
1135 eolmode = ui.config('patch', 'eol', 'strict') |
|
1136 try: |
|
1137 eol = {'strict': None, 'crlf': '\r\n', 'lf': '\n'}[eolmode.lower()] |
|
1138 except KeyError: |
|
1139 raise util.Abort(_('Unsupported line endings type: %s') % eolmode) |
|
1140 |
1110 try: |
1141 try: |
1111 fp = file(patchobj, 'rb') |
1142 fp = file(patchobj, 'rb') |
1112 except TypeError: |
1143 except TypeError: |
1113 fp = patchobj |
1144 fp = patchobj |
1114 if cwd: |
1145 if cwd: |
1115 curdir = os.getcwd() |
1146 curdir = os.getcwd() |
1116 os.chdir(cwd) |
1147 os.chdir(cwd) |
1117 try: |
1148 try: |
1118 ret = applydiff(ui, fp, files, strip=strip) |
1149 ret = applydiff(ui, fp, files, strip=strip, eol=eol) |
1119 finally: |
1150 finally: |
1120 if cwd: |
1151 if cwd: |
1121 os.chdir(curdir) |
1152 os.chdir(curdir) |
1122 if ret < 0: |
1153 if ret < 0: |
1123 raise PatchError |
1154 raise PatchError |
1124 return ret > 0 |
1155 return ret > 0 |
1125 |
1156 |
1126 def patch(patchname, ui, strip=1, cwd=None, files={}): |
1157 def patch(patchname, ui, strip=1, cwd=None, files={}, eolmode='strict'): |
1127 """apply <patchname> to the working directory. |
1158 """Apply <patchname> to the working directory. |
1128 returns whether patch was applied with fuzz factor.""" |
1159 |
|
1160 'eolmode' specifies how end of lines should be handled. It can be: |
|
1161 - 'strict': inputs are read in binary mode, EOLs are preserved |
|
1162 - 'crlf': EOLs are ignored when patching and reset to CRLF |
|
1163 - 'lf': EOLs are ignored when patching and reset to LF |
|
1164 - None: get it from user settings, default to 'strict' |
|
1165 'eolmode' is ignored when using an external patcher program. |
|
1166 |
|
1167 Returns whether patch was applied with fuzz factor. |
|
1168 """ |
1129 patcher = ui.config('ui', 'patch') |
1169 patcher = ui.config('ui', 'patch') |
1130 args = [] |
1170 args = [] |
1131 try: |
1171 try: |
1132 if patcher: |
1172 if patcher: |
1133 return externalpatch(patcher, args, patchname, ui, strip, cwd, |
1173 return externalpatch(patcher, args, patchname, ui, strip, cwd, |
1134 files) |
1174 files) |
1135 else: |
1175 else: |
1136 try: |
1176 try: |
1137 return internalpatch(patchname, ui, strip, cwd, files) |
1177 return internalpatch(patchname, ui, strip, cwd, files, eolmode) |
1138 except NoHunks: |
1178 except NoHunks: |
1139 patcher = util.find_exe('gpatch') or util.find_exe('patch') or 'patch' |
1179 patcher = util.find_exe('gpatch') or util.find_exe('patch') or 'patch' |
1140 ui.debug(_('no valid hunks found; trying with %r instead\n') % |
1180 ui.debug(_('no valid hunks found; trying with %r instead\n') % |
1141 patcher) |
1181 patcher) |
1142 if util.needbinarypatch(): |
1182 if util.needbinarypatch(): |