diff --git a/doc/hgrc.5.txt b/doc/hgrc.5.txt --- a/doc/hgrc.5.txt +++ b/doc/hgrc.5.txt @@ -646,12 +646,13 @@ command or with Mercurial Queues extensi ``eol`` When set to 'strict' patch content and patched files end of lines - are preserved. When set to ``lf`` or ``crlf``, both files end of lines - are ignored when patching and the result line endings are + are preserved. When set to ``lf`` or ``crlf``, both files end of + lines are ignored when patching and the result line endings are normalized to either LF (Unix) or CRLF (Windows). When set to ``auto``, end of lines are again ignored while patching but line endings in patched files are normalized to their original setting - on a per-file basis. + on a per-file basis. If target file does not exist or has no end + of line, patch line endings are preserved. Default: strict. diff --git a/mercurial/patch.py b/mercurial/patch.py --- a/mercurial/patch.py +++ b/mercurial/patch.py @@ -321,14 +321,14 @@ class patchfile(object): else: fp = self.opener(fname, 'w') try: - if self.eolmode == 'auto' and self.eol: + if self.eolmode == 'auto': eol = self.eol elif self.eolmode == 'crlf': eol = '\r\n' else: eol = '\n' - if self.eolmode != 'strict' and eol != '\n': + if self.eolmode != 'strict' and eol and eol != '\n': for l in lines: if l and l[-1] == '\n': l = l[:-1] + eol @@ -433,6 +433,15 @@ class patchfile(object): self.dirty = 1 return 0 + horig = h + if self.eolmode == 'auto' and self.eol: + # If eolmode == 'auto' and target file exists and has line + # endings we have to normalize input data before patching. + # Otherwise, patchfile operates in 'strict' mode. If + # eolmode is set to 'crlf' or 'lf', input hunk is already + # normalized to avoid data copy. + h = h.getnormalized() + # fast case first, no offsets, no fuzz old = h.old() # patch starts counting at 1 unless we are adding the file @@ -488,7 +497,7 @@ class patchfile(object): return fuzzlen self.printfile(True) self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start)) - self.rej.append(h) + self.rej.append(horig) return -1 class hunk(object): @@ -500,13 +509,39 @@ class hunk(object): self.b = [] self.starta = self.lena = None self.startb = self.lenb = None - if context: - self.read_context_hunk(lr) - else: - self.read_unified_hunk(lr) + if lr is not None: + if context: + self.read_context_hunk(lr) + else: + self.read_unified_hunk(lr) self.create = create self.remove = remove and not create + def getnormalized(self): + """Return a copy with line endings normalized to LF.""" + + def normalize(lines): + nlines = [] + for line in lines: + if line.endswith('\r\n'): + line = line[:-2] + '\n' + nlines.append(line) + return nlines + + # Dummy object, it is rebuilt manually + nh = hunk(self.desc, self.number, None, None, False, False) + nh.number = self.number + nh.desc = self.desc + nh.a = normalize(self.a) + nh.b = normalize(self.b) + nh.starta = self.starta + nh.startb = self.startb + nh.lena = self.lena + nh.lenb = self.lenb + nh.create = self.create + nh.remove = self.remove + return nh + def read_unified_hunk(self, lr): m = unidesc.match(self.desc) if not m: @@ -974,7 +1009,10 @@ def applydiff(ui, fp, changed, strip=1, current_file = None gitpatches = None opener = util.opener(os.getcwd()) - textmode = eolmode != 'strict' + # In 'auto' mode, we must preserve original eols if target file + # eols are undefined. Otherwise, hunk data will be normalized + # later. + textmode = eolmode not in ('strict', 'auto') def closefile(): if not current_file: diff --git a/tests/test-import-eol b/tests/test-import-eol --- a/tests/test-import-eol +++ b/tests/test-import-eol @@ -57,6 +57,21 @@ hg --traceback --config patch.eol='auto' python -c 'print repr(file("a","rb").read())' hg st +echo % auto EOL on new file or source without any EOL +python -c 'file("noeol", "wb").write("noeol")' +hg add noeol +hg commit -m 'add noeol' +python -c 'file("noeol", "wb").write("noeol\r\nnoeol\n")' +python -c 'file("neweol", "wb").write("neweol\nneweol\r\n")' +hg add neweol +hg diff --git > noeol.diff +hg revert --no-backup noeol neweol +rm neweol +hg --traceback --config patch.eol='auto' import -m noeol noeol.diff +python -c 'print repr(file("noeol","rb").read())' +python -c 'print repr(file("neweol","rb").read())' +hg st + # Test --eol and binary patches python -c 'file("b", "wb").write("a\x00\nb")' hg ci -Am addb diff --git a/tests/test-import-eol.out b/tests/test-import-eol.out --- a/tests/test-import-eol.out +++ b/tests/test-import-eol.out @@ -17,6 +17,10 @@ applying eol.diff % auto EOL on CRLF file applying eol.diff 'a\r\nyyyy\r\ncc\r\n\r\nd\r\ne' +% auto EOL on new file or source without any EOL +applying noeol.diff +'noeol\r\nnoeol\n' +'neweol\nneweol\r\n' adding b % binary patch with --eol applying bin.diff