Mercurial > public > mercurial-scm > hg
comparison mercurial/patch.py @ 5650:5d3e2f918d65
patch: move diff parsing in iterhunks generator
author | Patrick Mezard <pmezard@gmail.com> |
---|---|
date | Mon, 17 Dec 2007 23:06:01 +0100 |
parents | a583117b536a |
children | e11940d84606 |
comparison
equal
deleted
inserted
replaced
5649:a583117b536a | 5650:5d3e2f918d65 |
---|---|
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 |