836 l = self.buf[0] |
836 l = self.buf[0] |
837 del self.buf[0] |
837 del self.buf[0] |
838 return l |
838 return l |
839 return self.fp.readline() |
839 return self.fp.readline() |
840 |
840 |
841 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False, |
841 def iterhunks(ui, fp, sourcefile=None): |
842 rejmerge=None, updatedir=None): |
842 """Read a patch and yield the following events: |
843 """reads a patch from fp and tries to apply it. The dict 'changed' is |
843 - ("file", afile, bfile, firsthunk): select a new target file. |
844 filled in with all of the filenames changed by the patch. Returns 0 |
844 - ("hunk", hunk): a new hunk is ready to be applied, follows a |
845 for a clean patch, -1 if any rejects were found and 1 if there was |
845 "file" event. |
846 any fuzz.""" |
846 - ("git", gitchanges): current diff is in git format, gitchanges |
847 |
847 maps filenames to gitpatch records. Unique event. |
848 def scangitpatch(fp, firstline, cwd=None): |
848 """ |
|
849 |
|
850 def scangitpatch(fp, firstline): |
849 '''git patches can modify a file, then copy that file to |
851 '''git patches can modify a file, then copy that file to |
850 a new file, but expect the source to be the unmodified form. |
852 a new file, but expect the source to be the unmodified form. |
851 So we scan the patch looking for that case so we can do |
853 So we scan the patch looking for that case so we can do |
852 the copies ahead of time.''' |
854 the copies ahead of time.''' |
853 |
855 |
856 pos = fp.tell() |
858 pos = fp.tell() |
857 except IOError: |
859 except IOError: |
858 fp = cStringIO.StringIO(fp.read()) |
860 fp = cStringIO.StringIO(fp.read()) |
859 |
861 |
860 (dopatch, gitpatches) = readgitpatch(fp, firstline) |
862 (dopatch, gitpatches) = readgitpatch(fp, firstline) |
861 for gp in gitpatches: |
|
862 if gp.op in ('COPY', 'RENAME'): |
|
863 copyfile(gp.oldpath, gp.path, basedir=cwd) |
|
864 |
|
865 fp.seek(pos) |
863 fp.seek(pos) |
866 |
864 |
867 return fp, dopatch, gitpatches |
865 return fp, dopatch, gitpatches |
868 |
866 |
|
867 changed = {} |
869 current_hunk = None |
868 current_hunk = None |
870 current_file = None |
|
871 afile = "" |
869 afile = "" |
872 bfile = "" |
870 bfile = "" |
873 state = None |
871 state = None |
874 hunknum = 0 |
872 hunknum = 0 |
875 rejects = 0 |
873 emitfile = False |
876 |
874 |
877 git = False |
875 git = False |
878 gitre = re.compile('diff --git (a/.*) (b/.*)') |
876 gitre = re.compile('diff --git (a/.*) (b/.*)') |
879 |
877 |
880 # our states |
878 # our states |
881 BFILE = 1 |
879 BFILE = 1 |
882 err = 0 |
|
883 context = None |
880 context = None |
884 lr = linereader(fp) |
881 lr = linereader(fp) |
885 dopatch = True |
882 dopatch = True |
886 gitworkdone = False |
883 gitworkdone = False |
887 |
|
888 def getpatchfile(afile, bfile, hunk): |
|
889 try: |
|
890 if sourcefile: |
|
891 targetfile = patchfile(ui, sourcefile) |
|
892 else: |
|
893 targetfile = selectfile(afile, bfile, hunk, |
|
894 strip, reverse) |
|
895 targetfile = patchfile(ui, targetfile) |
|
896 return targetfile |
|
897 except PatchError, err: |
|
898 ui.warn(str(err) + '\n') |
|
899 return None |
|
900 |
884 |
901 while True: |
885 while True: |
902 newfile = False |
886 newfile = False |
903 x = lr.readline() |
887 x = lr.readline() |
904 if not x: |
888 if not x: |
905 break |
889 break |
906 if current_hunk: |
890 if current_hunk: |
907 if x.startswith('\ '): |
891 if x.startswith('\ '): |
908 current_hunk.fix_newline() |
892 current_hunk.fix_newline() |
909 ret = current_file.apply(current_hunk, reverse) |
893 yield 'hunk', current_hunk |
910 if ret >= 0: |
|
911 changed.setdefault(current_file.fname, (None, None)) |
|
912 if ret > 0: |
|
913 err = 1 |
|
914 current_hunk = None |
894 current_hunk = None |
915 gitworkdone = False |
895 gitworkdone = False |
916 if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or |
896 if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or |
917 ((context or context == None) and x.startswith('***************')))): |
897 ((context or context == None) and x.startswith('***************')))): |
918 try: |
898 try: |
922 except PatchError, err: |
902 except PatchError, err: |
923 ui.debug(err) |
903 ui.debug(err) |
924 current_hunk = None |
904 current_hunk = None |
925 continue |
905 continue |
926 hunknum += 1 |
906 hunknum += 1 |
927 if not current_file: |
907 if emitfile: |
928 current_file = getpatchfile(afile, bfile, current_hunk) |
908 emitfile = False |
929 if not current_file: |
909 yield 'file', (afile, bfile, current_hunk) |
930 current_file, current_hunk = None, None |
|
931 rejects += 1 |
|
932 continue |
|
933 elif state == BFILE and x.startswith('GIT binary patch'): |
910 elif state == BFILE and x.startswith('GIT binary patch'): |
934 current_hunk = binhunk(changed[bfile[2:]][1]) |
911 current_hunk = binhunk(changed[bfile[2:]][1]) |
935 hunknum += 1 |
912 hunknum += 1 |
936 if not current_file: |
913 if emitfile: |
937 current_file = getpatchfile(afile, bfile, current_hunk) |
914 emitfile = False |
938 if not current_file: |
915 yield 'file', (afile, bfile, current_hunk) |
939 current_file, current_hunk = None, None |
|
940 rejects += 1 |
|
941 continue |
|
942 current_hunk.extract(fp) |
916 current_hunk.extract(fp) |
943 elif x.startswith('diff --git'): |
917 elif x.startswith('diff --git'): |
944 # check for git diff, scanning the whole patch file if needed |
918 # check for git diff, scanning the whole patch file if needed |
945 m = gitre.match(x) |
919 m = gitre.match(x) |
946 if m: |
920 if m: |
947 afile, bfile = m.group(1, 2) |
921 afile, bfile = m.group(1, 2) |
948 if not git: |
922 if not git: |
949 git = True |
923 git = True |
950 fp, dopatch, gitpatches = scangitpatch(fp, x) |
924 fp, dopatch, gitpatches = scangitpatch(fp, x) |
|
925 yield 'git', gitpatches |
951 for gp in gitpatches: |
926 for gp in gitpatches: |
952 changed[gp.path] = (gp.op, gp) |
927 changed[gp.path] = (gp.op, gp) |
953 # else error? |
928 # else error? |
954 # copy/rename + modify should modify target, not source |
929 # copy/rename + modify should modify target, not source |
955 if changed.get(bfile[2:], (None, None))[0] in ('COPY', |
930 if changed.get(bfile[2:], (None, None))[0] in ('COPY', |
982 context = True |
957 context = True |
983 afile = parsefilename(x) |
958 afile = parsefilename(x) |
984 bfile = parsefilename(l2) |
959 bfile = parsefilename(l2) |
985 |
960 |
986 if newfile: |
961 if newfile: |
987 if current_file: |
962 emitfile = True |
988 current_file.close() |
|
989 if rejmerge: |
|
990 rejmerge(current_file) |
|
991 rejects += len(current_file.rej) |
|
992 state = BFILE |
963 state = BFILE |
993 current_file = None |
|
994 hunknum = 0 |
964 hunknum = 0 |
995 if current_hunk: |
965 if current_hunk: |
996 if current_hunk.complete(): |
966 if current_hunk.complete(): |
|
967 yield 'hunk', current_hunk |
|
968 else: |
|
969 raise PatchError(_("malformed patch %s %s") % (afile, |
|
970 current_hunk.desc)) |
|
971 |
|
972 if hunknum == 0 and dopatch and not gitworkdone: |
|
973 raise NoHunks |
|
974 |
|
975 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False, |
|
976 rejmerge=None, updatedir=None): |
|
977 """reads a patch from fp and tries to apply it. The dict 'changed' is |
|
978 filled in with all of the filenames changed by the patch. Returns 0 |
|
979 for a clean patch, -1 if any rejects were found and 1 if there was |
|
980 any fuzz.""" |
|
981 |
|
982 rejects = 0 |
|
983 err = 0 |
|
984 current_file = None |
|
985 gitpatches = None |
|
986 |
|
987 def closefile(): |
|
988 if not current_file: |
|
989 return 0 |
|
990 current_file.close() |
|
991 if rejmerge: |
|
992 rejmerge(current_file) |
|
993 return len(current_file.rej) |
|
994 |
|
995 for state, values in iterhunks(ui, fp, sourcefile): |
|
996 if state == 'hunk': |
|
997 if not current_file: |
|
998 continue |
|
999 current_hunk = values |
997 ret = current_file.apply(current_hunk, reverse) |
1000 ret = current_file.apply(current_hunk, reverse) |
998 if ret >= 0: |
1001 if ret >= 0: |
999 changed.setdefault(current_file.fname, (None, None)) |
1002 changed.setdefault(current_file.fname, (None, None)) |
1000 if ret > 0: |
1003 if ret > 0: |
1001 err = 1 |
1004 err = 1 |
|
1005 elif state == 'file': |
|
1006 rejects += closefile() |
|
1007 afile, bfile, first_hunk = values |
|
1008 try: |
|
1009 if sourcefile: |
|
1010 current_file = patchfile(ui, sourcefile) |
|
1011 else: |
|
1012 current_file = selectfile(afile, bfile, first_hunk, |
|
1013 strip, reverse) |
|
1014 current_file = patchfile(ui, current_file) |
|
1015 except PatchError, err: |
|
1016 ui.warn(str(err) + '\n') |
|
1017 current_file, current_hunk = None, None |
|
1018 rejects += 1 |
|
1019 continue |
|
1020 elif state == 'git': |
|
1021 gitpatches = values |
|
1022 for gp in gitpatches: |
|
1023 if gp.op in ('COPY', 'RENAME'): |
|
1024 copyfile(gp.oldpath, gp.path) |
|
1025 changed[gp.path] = (gp.op, gp) |
1002 else: |
1026 else: |
1003 fname = current_file and current_file.fname or None |
1027 raise util.Abort(_('unsupported parser state: %s') % state) |
1004 raise PatchError(_("malformed patch %s %s") % (fname, |
1028 |
1005 current_hunk.desc)) |
1029 rejects += closefile() |
1006 if current_file: |
1030 |
1007 current_file.close() |
1031 if updatedir and gitpatches: |
1008 if rejmerge: |
|
1009 rejmerge(current_file) |
|
1010 rejects += len(current_file.rej) |
|
1011 |
|
1012 if not rejects and hunknum == 0 and dopatch and not gitworkdone: |
|
1013 raise NoHunks |
|
1014 if updatedir and git: |
|
1015 updatedir(gitpatches) |
1032 updatedir(gitpatches) |
1016 if rejects: |
1033 if rejects: |
1017 return -1 |
1034 return -1 |
1018 return err |
1035 return err |
1019 |
1036 |