Mercurial > hg
comparison mercurial/patch.py @ 24269:9a745ced79a9
record: move filterpatch from record to patch
Part of a series of patches to move record from hgext to core
author | Laurent Charignon <lcharignon@fb.com> |
---|---|
date | Tue, 10 Mar 2015 14:42:07 -0700 |
parents | cf7d252d8c30 |
children | 6ddc86eedc3b |
comparison
equal
deleted
inserted
replaced
24268:cf7d252d8c30 | 24269:9a745ced79a9 |
---|---|
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com> | 4 # Copyright 2007 Chris Mason <chris.mason@oracle.com> |
5 # | 5 # |
6 # This software may be used and distributed according to the terms of the | 6 # This software may be used and distributed according to the terms of the |
7 # GNU General Public License version 2 or any later version. | 7 # GNU General Public License version 2 or any later version. |
8 | 8 |
9 import cStringIO, email, os, errno, re, posixpath | 9 import cStringIO, email, os, errno, re, posixpath, copy |
10 import tempfile, zlib, shutil | 10 import tempfile, zlib, shutil |
11 # On python2.4 you have to import these by name or they fail to | 11 # On python2.4 you have to import these by name or they fail to |
12 # load. This was not a problem on Python 2.7. | 12 # load. This was not a problem on Python 2.7. |
13 import email.Generator | 13 import email.Generator |
14 import email.Parser | 14 import email.Parser |
906 return self.header.filename() | 906 return self.header.filename() |
907 | 907 |
908 def __repr__(self): | 908 def __repr__(self): |
909 return '<hunk %r@%d>' % (self.filename(), self.fromline) | 909 return '<hunk %r@%d>' % (self.filename(), self.fromline) |
910 | 910 |
911 def filterpatch(ui, headers): | |
912 """Interactively filter patch chunks into applied-only chunks""" | |
913 | |
914 def prompt(skipfile, skipall, query, chunk): | |
915 """prompt query, and process base inputs | |
916 | |
917 - y/n for the rest of file | |
918 - y/n for the rest | |
919 - ? (help) | |
920 - q (quit) | |
921 | |
922 Return True/False and possibly updated skipfile and skipall. | |
923 """ | |
924 newpatches = None | |
925 if skipall is not None: | |
926 return skipall, skipfile, skipall, newpatches | |
927 if skipfile is not None: | |
928 return skipfile, skipfile, skipall, newpatches | |
929 while True: | |
930 resps = _('[Ynesfdaq?]' | |
931 '$$ &Yes, record this change' | |
932 '$$ &No, skip this change' | |
933 '$$ &Edit this change manually' | |
934 '$$ &Skip remaining changes to this file' | |
935 '$$ Record remaining changes to this &file' | |
936 '$$ &Done, skip remaining changes and files' | |
937 '$$ Record &all changes to all remaining files' | |
938 '$$ &Quit, recording no changes' | |
939 '$$ &? (display help)') | |
940 r = ui.promptchoice("%s %s" % (query, resps)) | |
941 ui.write("\n") | |
942 if r == 8: # ? | |
943 for c, t in ui.extractchoices(resps)[1]: | |
944 ui.write('%s - %s\n' % (c, t.lower())) | |
945 continue | |
946 elif r == 0: # yes | |
947 ret = True | |
948 elif r == 1: # no | |
949 ret = False | |
950 elif r == 2: # Edit patch | |
951 if chunk is None: | |
952 ui.write(_('cannot edit patch for whole file')) | |
953 ui.write("\n") | |
954 continue | |
955 if chunk.header.binary(): | |
956 ui.write(_('cannot edit patch for binary file')) | |
957 ui.write("\n") | |
958 continue | |
959 # Patch comment based on the Git one (based on comment at end of | |
960 # http://mercurial.selenic.com/wiki/RecordExtension) | |
961 phelp = '---' + _(""" | |
962 To remove '-' lines, make them ' ' lines (context). | |
963 To remove '+' lines, delete them. | |
964 Lines starting with # will be removed from the patch. | |
965 | |
966 If the patch applies cleanly, the edited hunk will immediately be | |
967 added to the record list. If it does not apply cleanly, a rejects | |
968 file will be generated: you can use that when you try again. If | |
969 all lines of the hunk are removed, then the edit is aborted and | |
970 the hunk is left unchanged. | |
971 """) | |
972 (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-", | |
973 suffix=".diff", text=True) | |
974 ncpatchfp = None | |
975 try: | |
976 # Write the initial patch | |
977 f = os.fdopen(patchfd, "w") | |
978 chunk.header.write(f) | |
979 chunk.write(f) | |
980 f.write('\n'.join(['# ' + i for i in phelp.splitlines()])) | |
981 f.close() | |
982 # Start the editor and wait for it to complete | |
983 editor = ui.geteditor() | |
984 ui.system("%s \"%s\"" % (editor, patchfn), | |
985 environ={'HGUSER': ui.username()}, | |
986 onerr=util.Abort, errprefix=_("edit failed")) | |
987 # Remove comment lines | |
988 patchfp = open(patchfn) | |
989 ncpatchfp = cStringIO.StringIO() | |
990 for line in patchfp: | |
991 if not line.startswith('#'): | |
992 ncpatchfp.write(line) | |
993 patchfp.close() | |
994 ncpatchfp.seek(0) | |
995 newpatches = parsepatch(ncpatchfp) | |
996 finally: | |
997 os.unlink(patchfn) | |
998 del ncpatchfp | |
999 # Signal that the chunk shouldn't be applied as-is, but | |
1000 # provide the new patch to be used instead. | |
1001 ret = False | |
1002 elif r == 3: # Skip | |
1003 ret = skipfile = False | |
1004 elif r == 4: # file (Record remaining) | |
1005 ret = skipfile = True | |
1006 elif r == 5: # done, skip remaining | |
1007 ret = skipall = False | |
1008 elif r == 6: # all | |
1009 ret = skipall = True | |
1010 elif r == 7: # quit | |
1011 raise util.Abort(_('user quit')) | |
1012 return ret, skipfile, skipall, newpatches | |
1013 | |
1014 seen = set() | |
1015 applied = {} # 'filename' -> [] of chunks | |
1016 skipfile, skipall = None, None | |
1017 pos, total = 1, sum(len(h.hunks) for h in headers) | |
1018 for h in headers: | |
1019 pos += len(h.hunks) | |
1020 skipfile = None | |
1021 fixoffset = 0 | |
1022 hdr = ''.join(h.header) | |
1023 if hdr in seen: | |
1024 continue | |
1025 seen.add(hdr) | |
1026 if skipall is None: | |
1027 h.pretty(ui) | |
1028 msg = (_('examine changes to %s?') % | |
1029 _(' and ').join("'%s'" % f for f in h.files())) | |
1030 r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None) | |
1031 if not r: | |
1032 continue | |
1033 applied[h.filename()] = [h] | |
1034 if h.allhunks(): | |
1035 applied[h.filename()] += h.hunks | |
1036 continue | |
1037 for i, chunk in enumerate(h.hunks): | |
1038 if skipfile is None and skipall is None: | |
1039 chunk.pretty(ui) | |
1040 if total == 1: | |
1041 msg = _("record this change to '%s'?") % chunk.filename() | |
1042 else: | |
1043 idx = pos - len(h.hunks) + i | |
1044 msg = _("record change %d/%d to '%s'?") % (idx, total, | |
1045 chunk.filename()) | |
1046 r, skipfile, skipall, newpatches = prompt(skipfile, | |
1047 skipall, msg, chunk) | |
1048 if r: | |
1049 if fixoffset: | |
1050 chunk = copy.copy(chunk) | |
1051 chunk.toline += fixoffset | |
1052 applied[chunk.filename()].append(chunk) | |
1053 elif newpatches is not None: | |
1054 for newpatch in newpatches: | |
1055 for newhunk in newpatch.hunks: | |
1056 if fixoffset: | |
1057 newhunk.toline += fixoffset | |
1058 applied[newhunk.filename()].append(newhunk) | |
1059 else: | |
1060 fixoffset += chunk.removed - chunk.added | |
1061 return sum([h for h in applied.itervalues() | |
1062 if h[0].special() or len(h) > 1], []) | |
911 class hunk(object): | 1063 class hunk(object): |
912 def __init__(self, desc, num, lr, context): | 1064 def __init__(self, desc, num, lr, context): |
913 self.number = num | 1065 self.number = num |
914 self.desc = desc | 1066 self.desc = desc |
915 self.hunk = [desc] | 1067 self.hunk = [desc] |