From ff73ca923833900ff7ebb3e98b3ab838214ca32c Mon Sep 17 00:00:00 2001 From: Hisham Muhammad Date: Wed, 4 Oct 2017 20:50:14 -0300 Subject: 3.0: Patches can create and delete files. --- src/luarocks/build.lua | 3 +- src/luarocks/fs/lua.lua | 5 +- src/luarocks/tools/patch.lua | 306 ++++++++++++++++++++++++------------------- 3 files changed, 179 insertions(+), 135 deletions(-) (limited to 'src') diff --git a/src/luarocks/build.lua b/src/luarocks/build.lua index ceaa20dd..860cbdc1 100644 --- a/src/luarocks/build.lua +++ b/src/luarocks/build.lua @@ -91,7 +91,8 @@ function build.apply_patches(rockspec) extract_from_rockspec(build_spec.patches) for patch, patchdata in util.sortedpairs(build_spec.patches) do util.printout("Applying patch "..patch.."...") - local ok, err = fs.apply_patch(tostring(patch), patchdata) + local create_delete = rockspec:format_is_at_least("3.0") + local ok, err = fs.apply_patch(tostring(patch), patchdata, create_delete) if not ok then return nil, "Failed applying patch "..patch end diff --git a/src/luarocks/fs/lua.lua b/src/luarocks/fs/lua.lua index db5c675d..dbd4174d 100644 --- a/src/luarocks/fs/lua.lua +++ b/src/luarocks/fs/lua.lua @@ -810,13 +810,14 @@ end --- Apply a patch. -- @param patchname string: The filename of the patch. -- @param patchdata string or nil: The actual patch as a string. -function fs_lua.apply_patch(patchname, patchdata) +-- @param create_delete boolean: Support creating and deleting files in a patch. +function fs_lua.apply_patch(patchname, patchdata, create_delete) local p, all_ok = patch.read_patch(patchname, patchdata) if not all_ok then return nil, "Failed reading patch "..patchname end if p then - return patch.apply_patch(p, 1) + return patch.apply_patch(p, 1, create_delete) end end diff --git a/src/luarocks/tools/patch.lua b/src/luarocks/tools/patch.lua index f1a14578..30ed1419 100644 --- a/src/luarocks/tools/patch.lua +++ b/src/luarocks/tools/patch.lua @@ -84,13 +84,6 @@ local function file_copy(src, dest) return true end -local function overwrite_file(filename) - local fh = io.open(filename, "w+") - if not fh then return false end - fh:close() - return true -end - local function string_as_file(s) return { at = 0, @@ -162,6 +155,10 @@ local function match_linerange(line) return m1, m2, m3, m4 end +local function match_epoch(str) + return str:match("[^0-9]1969[^0-9]") or str:match("[^0-9]1970[^0-9]") +end + function patch.read_patch(filename, data) -- define possible file regions that will direct the parser flow local state = 'header' @@ -173,7 +170,7 @@ function patch.read_patch(filename, data) local all_ok = true local lineends = {lf=0, crlf=0, cr=0} - local files = {source={}, target={}, hunks={}, fileends={}, hunkends={}} + local files = {source={}, target={}, epoch={}, hunks={}, fileends={}, hunkends={}} local nextfileno = 0 local nexthunkno = 0 --: even if index starts with 0 user messages -- number hunks from 1 @@ -268,12 +265,6 @@ function patch.read_patch(filename, data) warning(format("inconsistent line ends in patch hunks for %s", files.source[nextfileno])) end - if debugmode then - local debuglines = {crlf=ends.crlf, lf=ends.lf, cr=ends.cr, - file=files.target[nextfileno], hunk=nexthunkno} - debug(format("crlf: %(crlf)d lf: %(lf)d cr: %(cr)d\t " .. - "- file: %(file)s hunk: %(hunk)d", debuglines)) - end end -- state is 'hunkbody' or 'hunkskip' end @@ -304,12 +295,15 @@ function patch.read_patch(filename, data) -- Accept a space as a terminator, like GNU patch does. -- Breaks patches containing filenames with spaces... -- FIXME Figure out what does GNU patch do in those cases. - local match = line:match("^%-%-%- ([^ \t\r\n]+)") + local match, rest = line:match("^%-%-%- ([^ \t\r\n]+)(.*)") if not match then all_ok = false warning(format("skipping invalid filename at line %d", lineno+1)) state = 'header' else + if match_epoch(rest) then + files.epoch[nextfileno + 1] = true + end table.insert(files.source, match) end elseif not startswith(line, "+++ ") then @@ -338,8 +332,8 @@ function patch.read_patch(filename, data) -- Accept a space as a terminator, like GNU patch does. -- Breaks patches containing filenames with spaces... -- FIXME Figure out what does GNU patch do in those cases. - local re_filename = "^%+%+%+ ([^ \t\r\n]+)" - local match = line:match(re_filename) + local re_filename = "^%+%+%+ ([^ \t\r\n]+)(.*)$" + local match, rest = line:match(re_filename) if not match then all_ok = false warning(format( @@ -349,6 +343,9 @@ function patch.read_patch(filename, data) else table.insert(files.target, match) nextfileno = nextfileno + 1 + if match_epoch(rest) then + files.epoch[nextfileno] = true + end nexthunkno = 0 table.insert(files.hunks, {}) table.insert(files.hunkends, table_copy(lineends)) @@ -568,143 +565,188 @@ local function strip_dirs(filename, strip) return filename end -function patch.apply_patch(the_patch, strip) - local all_ok = true - local total = #the_patch.source - for fileno, filename in ipairs(the_patch.source) do - if filename == "/dev/null" then - filename = strip_dirs(the_patch.target[fileno], strip) - assert(overwrite_file(filename)) - else - filename = strip_dirs(filename, strip) +local function write_new_file(filename, hunk) + local fh = io.open(filename, "wb") + if not fh then return false end + for _, hline in ipairs(hunk.text) do + if not hline:sub(1,1) == "+" then + return false, "malformed patch" end - local continue - local f2patch = filename - if not exists(f2patch) then - f2patch = strip_dirs(the_patch.target[fileno], strip) - f2patch = fs.absolute_name(f2patch) - if not exists(f2patch) then --FIX:if f2patch nil - warning(format("source/target file does not exist\n--- %s\n+++ %s", - filename, f2patch)) - all_ok = false - continue = true - end + fh:write(hline:sub(2)) + end + fh:close() + return true +end + +local function patch_file(source, target, epoch, hunks, strip, create_delete) + local create_file = false + if create_delete then + local is_src_epoch = epoch and #hunks == 1 and hunks[1].startsrc == 0 and hunks[1].linessrc == 0 + if is_src_epoch or source == "/dev/null" then + info(format("will create %s", target)) + create_file = true end - if not continue and not isfile(f2patch) then - warning(format("not a file - %s", f2patch)) - all_ok = false - continue = true + end + if create_file then + return write_new_file(fs.absolute_name(strip_dirs(target, strip)), hunks[1]) + end + source = strip_dirs(source, strip) + local f2patch = source + if not exists(f2patch) then + f2patch = strip_dirs(target, strip) + f2patch = fs.absolute_name(f2patch) + if not exists(f2patch) then --FIX:if f2patch nil + warning(format("source/target file does not exist\n--- %s\n+++ %s", + source, f2patch)) + return false end - if not continue then + end + if not isfile(f2patch) then + warning(format("not a file - %s", f2patch)) + return false + end - filename = f2patch + source = f2patch + + -- validate before patching + local file = load_file(source) + local hunkno = 1 + local hunk = hunks[hunkno] + local hunkfind = {} + local validhunks = 0 + local canpatch = false + local hunklineno + if not file then + return nil, "failed reading file " .. source + end - info(format("processing %d/%d:\t %s", fileno, total, filename)) + if create_delete then + if epoch and #hunks == 1 and hunks[1].starttgt == 0 and hunks[1].linestgt == 0 then + local ok = os.remove(source) + if not ok then + return false + end + info(format("successfully removed %s", source)) + return true + end + end - -- validate before patching - local hunks = the_patch.hunks[fileno] - local file = load_file(filename) - local hunkno = 1 - local hunk = hunks[hunkno] - local hunkfind = {} - local validhunks = 0 - local canpatch = false - local hunklineno - local isbreak - local lineno = 0 - - find_hunks(file, hunks) - - for _, line in ipairs(file) do - lineno = lineno + 1 - local continue - if not hunk or lineno < hunk.startsrc then - continue = true - elseif lineno == hunk.startsrc then - hunkfind = {} - for _,x in ipairs(hunk.text) do - if x:sub(1,1) == ' ' or x:sub(1,1) == '-' then - hunkfind[#hunkfind+1] = endlstrip(x:sub(2)) - end - end - hunklineno = 1 + find_hunks(file, hunks) - -- todo \ No newline at end of file - end - -- check hunks in source file - if not continue and lineno < hunk.startsrc + #hunkfind - 1 then - if endlstrip(line) == hunkfind[hunklineno] then - hunklineno = hunklineno + 1 - else - debug(format("hunk no.%d doesn't match source file %s", - hunkno, filename)) - -- file may be already patched, but check other hunks anyway - hunkno = hunkno + 1 - if hunkno <= #hunks then - hunk = hunks[hunkno] - continue = true - else - isbreak = true; break - end + local function process_line(line, lineno) + if not hunk or lineno < hunk.startsrc then + return false + end + if lineno == hunk.startsrc then + hunkfind = {} + for _,x in ipairs(hunk.text) do + if x:sub(1,1) == ' ' or x:sub(1,1) == '-' then + hunkfind[#hunkfind+1] = endlstrip(x:sub(2)) end end - -- check if processed line is the last line - if not continue and lineno == hunk.startsrc + #hunkfind - 1 then - debug(format("file %s hunk no.%d -- is ready to be patched", - filename, hunkno)) + hunklineno = 1 + + -- todo \ No newline at end of file + end + -- check hunks in source file + if lineno < hunk.startsrc + #hunkfind - 1 then + if endlstrip(line) == hunkfind[hunklineno] then + hunklineno = hunklineno + 1 + else + debug(format("hunk no.%d doesn't match source file %s", + hunkno, source)) + -- file may be already patched, but check other hunks anyway hunkno = hunkno + 1 - validhunks = validhunks + 1 if hunkno <= #hunks then hunk = hunks[hunkno] + return false else - if validhunks == #hunks then - -- patch file - canpatch = true - isbreak = true; break - end + return true end end end - if not isbreak then + -- check if processed line is the last line + if lineno == hunk.startsrc + #hunkfind - 1 then + debug(format("file %s hunk no.%d -- is ready to be patched", + source, hunkno)) + hunkno = hunkno + 1 + validhunks = validhunks + 1 if hunkno <= #hunks then - warning(format("premature end of source file %s at hunk %d", - filename, hunkno)) - all_ok = false - end - end - if validhunks < #hunks then - if check_patched(file, hunks) then - warning(format("already patched %s", filename)) + hunk = hunks[hunkno] else - warning(format("source file is different - %s", filename)) - all_ok = false - end - end - if canpatch then - local backupname = filename .. ".orig" - if exists(backupname) then - warning(format("can't backup original file to %s - aborting", - backupname)) - all_ok = false - else - assert(os.rename(filename, backupname)) - if patch_hunks(backupname, filename, hunks) then - warning(format("successfully patched %s", filename)) - assert(os.remove(backupname)) - else - warning(format("error patching file %s", filename)) - assert(file_copy(filename, filename .. ".invalid")) - warning(format("invalid version is saved to %s", - filename .. ".invalid")) - -- todo: proper rejects - assert(os.rename(backupname, filename)) - all_ok = false + if validhunks == #hunks then + -- patch file + canpatch = true + return true end end end + return false + end + + local done = false + for lineno, line in ipairs(file) do + done = process_line(line, lineno) + if done then + break + end + end + if not done then + if hunkno <= #hunks and not create_file then + warning(format("premature end of source file %s at hunk %d", + source, hunkno)) + return false + end + end + if validhunks < #hunks then + if check_patched(file, hunks) then + warning(format("already patched %s", source)) + elseif not create_file then + warning(format("source file is different - %s", source)) + return false + end + end + if not canpatch then + return true + end + local backupname = source .. ".orig" + if exists(backupname) then + warning(format("can't backup original file to %s - aborting", + backupname)) + return false + end + local ok = os.rename(source, backupname) + if not ok then + warning(format("failed backing up %s when patching", source)) + return false + end + ok = patch_hunks(backupname, source, hunks) + if not ok then + warning(format("error patching file %s", source)) + if file_copy(source, source .. ".invalid") then + warning(format("invalid version is saved to %s", + source .. ".invalid")) + -- todo: proper rejects + os.rename(backupname, source) + end + return false + end + info(format("successfully patched %s", source)) + os.remove(backupname) + return true +end - end -- if not continue - end -- for +function patch.apply_patch(the_patch, strip, create_delete) + local all_ok = true + local total = #the_patch.source + for fileno, source in ipairs(the_patch.source) do + local target = the_patch.target[fileno] + local hunks = the_patch.hunks[fileno] + local epoch = the_patch.epoch[fileno] + info(format("processing %d/%d:\t %s", fileno, total, source)) + local ok = patch_file(source, target, epoch, hunks, strip, create_delete) + all_ok = all_ok and ok + end -- todo: check for premature eof return all_ok end -- cgit v1.2.3-55-g6feb