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] |