aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHisham Muhammad <hisham@gobolinux.org>2017-10-04 20:50:14 -0300
committerHisham Muhammad <hisham@gobolinux.org>2017-10-04 20:53:33 -0300
commitff73ca923833900ff7ebb3e98b3ab838214ca32c (patch)
tree5b5eca98b876805fc9cbd56c7a96762b8b830e25 /src
parentc4b66299b1259a1e3d6bfef83d912332ccc58061 (diff)
downloadluarocks-ff73ca923833900ff7ebb3e98b3ab838214ca32c.tar.gz
luarocks-ff73ca923833900ff7ebb3e98b3ab838214ca32c.tar.bz2
luarocks-ff73ca923833900ff7ebb3e98b3ab838214ca32c.zip
3.0: Patches can create and delete files.
Diffstat (limited to 'src')
-rw-r--r--src/luarocks/build.lua3
-rw-r--r--src/luarocks/fs/lua.lua5
-rw-r--r--src/luarocks/tools/patch.lua306
3 files changed, 179 insertions, 135 deletions
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)
91 extract_from_rockspec(build_spec.patches) 91 extract_from_rockspec(build_spec.patches)
92 for patch, patchdata in util.sortedpairs(build_spec.patches) do 92 for patch, patchdata in util.sortedpairs(build_spec.patches) do
93 util.printout("Applying patch "..patch.."...") 93 util.printout("Applying patch "..patch.."...")
94 local ok, err = fs.apply_patch(tostring(patch), patchdata) 94 local create_delete = rockspec:format_is_at_least("3.0")
95 local ok, err = fs.apply_patch(tostring(patch), patchdata, create_delete)
95 if not ok then 96 if not ok then
96 return nil, "Failed applying patch "..patch 97 return nil, "Failed applying patch "..patch
97 end 98 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
810--- Apply a patch. 810--- Apply a patch.
811-- @param patchname string: The filename of the patch. 811-- @param patchname string: The filename of the patch.
812-- @param patchdata string or nil: The actual patch as a string. 812-- @param patchdata string or nil: The actual patch as a string.
813function fs_lua.apply_patch(patchname, patchdata) 813-- @param create_delete boolean: Support creating and deleting files in a patch.
814function fs_lua.apply_patch(patchname, patchdata, create_delete)
814 local p, all_ok = patch.read_patch(patchname, patchdata) 815 local p, all_ok = patch.read_patch(patchname, patchdata)
815 if not all_ok then 816 if not all_ok then
816 return nil, "Failed reading patch "..patchname 817 return nil, "Failed reading patch "..patchname
817 end 818 end
818 if p then 819 if p then
819 return patch.apply_patch(p, 1) 820 return patch.apply_patch(p, 1, create_delete)
820 end 821 end
821end 822end
822 823
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)
84 return true 84 return true
85end 85end
86 86
87local function overwrite_file(filename)
88 local fh = io.open(filename, "w+")
89 if not fh then return false end
90 fh:close()
91 return true
92end
93
94local function string_as_file(s) 87local function string_as_file(s)
95 return { 88 return {
96 at = 0, 89 at = 0,
@@ -162,6 +155,10 @@ local function match_linerange(line)
162 return m1, m2, m3, m4 155 return m1, m2, m3, m4
163end 156end
164 157
158local function match_epoch(str)
159 return str:match("[^0-9]1969[^0-9]") or str:match("[^0-9]1970[^0-9]")
160end
161
165function patch.read_patch(filename, data) 162function patch.read_patch(filename, data)
166 -- define possible file regions that will direct the parser flow 163 -- define possible file regions that will direct the parser flow
167 local state = 'header' 164 local state = 'header'
@@ -173,7 +170,7 @@ function patch.read_patch(filename, data)
173 170
174 local all_ok = true 171 local all_ok = true
175 local lineends = {lf=0, crlf=0, cr=0} 172 local lineends = {lf=0, crlf=0, cr=0}
176 local files = {source={}, target={}, hunks={}, fileends={}, hunkends={}} 173 local files = {source={}, target={}, epoch={}, hunks={}, fileends={}, hunkends={}}
177 local nextfileno = 0 174 local nextfileno = 0
178 local nexthunkno = 0 --: even if index starts with 0 user messages 175 local nexthunkno = 0 --: even if index starts with 0 user messages
179 -- number hunks from 1 176 -- number hunks from 1
@@ -268,12 +265,6 @@ function patch.read_patch(filename, data)
268 warning(format("inconsistent line ends in patch hunks for %s", 265 warning(format("inconsistent line ends in patch hunks for %s",
269 files.source[nextfileno])) 266 files.source[nextfileno]))
270 end 267 end
271 if debugmode then
272 local debuglines = {crlf=ends.crlf, lf=ends.lf, cr=ends.cr,
273 file=files.target[nextfileno], hunk=nexthunkno}
274 debug(format("crlf: %(crlf)d lf: %(lf)d cr: %(cr)d\t " ..
275 "- file: %(file)s hunk: %(hunk)d", debuglines))
276 end
277 end 268 end
278 -- state is 'hunkbody' or 'hunkskip' 269 -- state is 'hunkbody' or 'hunkskip'
279 end 270 end
@@ -304,12 +295,15 @@ function patch.read_patch(filename, data)
304 -- Accept a space as a terminator, like GNU patch does. 295 -- Accept a space as a terminator, like GNU patch does.
305 -- Breaks patches containing filenames with spaces... 296 -- Breaks patches containing filenames with spaces...
306 -- FIXME Figure out what does GNU patch do in those cases. 297 -- FIXME Figure out what does GNU patch do in those cases.
307 local match = line:match("^%-%-%- ([^ \t\r\n]+)") 298 local match, rest = line:match("^%-%-%- ([^ \t\r\n]+)(.*)")
308 if not match then 299 if not match then
309 all_ok = false 300 all_ok = false
310 warning(format("skipping invalid filename at line %d", lineno+1)) 301 warning(format("skipping invalid filename at line %d", lineno+1))
311 state = 'header' 302 state = 'header'
312 else 303 else
304 if match_epoch(rest) then
305 files.epoch[nextfileno + 1] = true
306 end
313 table.insert(files.source, match) 307 table.insert(files.source, match)
314 end 308 end
315 elseif not startswith(line, "+++ ") then 309 elseif not startswith(line, "+++ ") then
@@ -338,8 +332,8 @@ function patch.read_patch(filename, data)
338 -- Accept a space as a terminator, like GNU patch does. 332 -- Accept a space as a terminator, like GNU patch does.
339 -- Breaks patches containing filenames with spaces... 333 -- Breaks patches containing filenames with spaces...
340 -- FIXME Figure out what does GNU patch do in those cases. 334 -- FIXME Figure out what does GNU patch do in those cases.
341 local re_filename = "^%+%+%+ ([^ \t\r\n]+)" 335 local re_filename = "^%+%+%+ ([^ \t\r\n]+)(.*)$"
342 local match = line:match(re_filename) 336 local match, rest = line:match(re_filename)
343 if not match then 337 if not match then
344 all_ok = false 338 all_ok = false
345 warning(format( 339 warning(format(
@@ -349,6 +343,9 @@ function patch.read_patch(filename, data)
349 else 343 else
350 table.insert(files.target, match) 344 table.insert(files.target, match)
351 nextfileno = nextfileno + 1 345 nextfileno = nextfileno + 1
346 if match_epoch(rest) then
347 files.epoch[nextfileno] = true
348 end
352 nexthunkno = 0 349 nexthunkno = 0
353 table.insert(files.hunks, {}) 350 table.insert(files.hunks, {})
354 table.insert(files.hunkends, table_copy(lineends)) 351 table.insert(files.hunkends, table_copy(lineends))
@@ -568,143 +565,188 @@ local function strip_dirs(filename, strip)
568 return filename 565 return filename
569end 566end
570 567
571function patch.apply_patch(the_patch, strip) 568local function write_new_file(filename, hunk)
572 local all_ok = true 569 local fh = io.open(filename, "wb")
573 local total = #the_patch.source 570 if not fh then return false end
574 for fileno, filename in ipairs(the_patch.source) do 571 for _, hline in ipairs(hunk.text) do
575 if filename == "/dev/null" then 572 if not hline:sub(1,1) == "+" then
576 filename = strip_dirs(the_patch.target[fileno], strip) 573 return false, "malformed patch"
577 assert(overwrite_file(filename))
578 else
579 filename = strip_dirs(filename, strip)
580 end 574 end
581 local continue 575 fh:write(hline:sub(2))
582 local f2patch = filename 576 end
583 if not exists(f2patch) then 577 fh:close()
584 f2patch = strip_dirs(the_patch.target[fileno], strip) 578 return true
585 f2patch = fs.absolute_name(f2patch) 579end
586 if not exists(f2patch) then --FIX:if f2patch nil 580
587 warning(format("source/target file does not exist\n--- %s\n+++ %s", 581local function patch_file(source, target, epoch, hunks, strip, create_delete)
588 filename, f2patch)) 582 local create_file = false
589 all_ok = false 583 if create_delete then
590 continue = true 584 local is_src_epoch = epoch and #hunks == 1 and hunks[1].startsrc == 0 and hunks[1].linessrc == 0
591 end 585 if is_src_epoch or source == "/dev/null" then
586 info(format("will create %s", target))
587 create_file = true
592 end 588 end
593 if not continue and not isfile(f2patch) then 589 end
594 warning(format("not a file - %s", f2patch)) 590 if create_file then
595 all_ok = false 591 return write_new_file(fs.absolute_name(strip_dirs(target, strip)), hunks[1])
596 continue = true 592 end
593 source = strip_dirs(source, strip)
594 local f2patch = source
595 if not exists(f2patch) then
596 f2patch = strip_dirs(target, strip)
597 f2patch = fs.absolute_name(f2patch)
598 if not exists(f2patch) then --FIX:if f2patch nil
599 warning(format("source/target file does not exist\n--- %s\n+++ %s",
600 source, f2patch))
601 return false
597 end 602 end
598 if not continue then 603 end
604 if not isfile(f2patch) then
605 warning(format("not a file - %s", f2patch))
606 return false
607 end
599 608
600 filename = f2patch 609 source = f2patch
610
611 -- validate before patching
612 local file = load_file(source)
613 local hunkno = 1
614 local hunk = hunks[hunkno]
615 local hunkfind = {}
616 local validhunks = 0
617 local canpatch = false
618 local hunklineno
619 if not file then
620 return nil, "failed reading file " .. source
621 end
601 622
602 info(format("processing %d/%d:\t %s", fileno, total, filename)) 623 if create_delete then
624 if epoch and #hunks == 1 and hunks[1].starttgt == 0 and hunks[1].linestgt == 0 then
625 local ok = os.remove(source)
626 if not ok then
627 return false
628 end
629 info(format("successfully removed %s", source))
630 return true
631 end
632 end
603 633
604 -- validate before patching 634 find_hunks(file, hunks)
605 local hunks = the_patch.hunks[fileno]
606 local file = load_file(filename)
607 local hunkno = 1
608 local hunk = hunks[hunkno]
609 local hunkfind = {}
610 local validhunks = 0
611 local canpatch = false
612 local hunklineno
613 local isbreak
614 local lineno = 0
615
616 find_hunks(file, hunks)
617
618 for _, line in ipairs(file) do
619 lineno = lineno + 1
620 local continue
621 if not hunk or lineno < hunk.startsrc then
622 continue = true
623 elseif lineno == hunk.startsrc then
624 hunkfind = {}
625 for _,x in ipairs(hunk.text) do
626 if x:sub(1,1) == ' ' or x:sub(1,1) == '-' then
627 hunkfind[#hunkfind+1] = endlstrip(x:sub(2))
628 end
629 end
630 hunklineno = 1
631 635
632 -- todo \ No newline at end of file 636 local function process_line(line, lineno)
633 end 637 if not hunk or lineno < hunk.startsrc then
634 -- check hunks in source file 638 return false
635 if not continue and lineno < hunk.startsrc + #hunkfind - 1 then 639 end
636 if endlstrip(line) == hunkfind[hunklineno] then 640 if lineno == hunk.startsrc then
637 hunklineno = hunklineno + 1 641 hunkfind = {}
638 else 642 for _,x in ipairs(hunk.text) do
639 debug(format("hunk no.%d doesn't match source file %s", 643 if x:sub(1,1) == ' ' or x:sub(1,1) == '-' then
640 hunkno, filename)) 644 hunkfind[#hunkfind+1] = endlstrip(x:sub(2))
641 -- file may be already patched, but check other hunks anyway
642 hunkno = hunkno + 1
643 if hunkno <= #hunks then
644 hunk = hunks[hunkno]
645 continue = true
646 else
647 isbreak = true; break
648 end
649 end 645 end
650 end 646 end
651 -- check if processed line is the last line 647 hunklineno = 1
652 if not continue and lineno == hunk.startsrc + #hunkfind - 1 then 648
653 debug(format("file %s hunk no.%d -- is ready to be patched", 649 -- todo \ No newline at end of file
654 filename, hunkno)) 650 end
651 -- check hunks in source file
652 if lineno < hunk.startsrc + #hunkfind - 1 then
653 if endlstrip(line) == hunkfind[hunklineno] then
654 hunklineno = hunklineno + 1
655 else
656 debug(format("hunk no.%d doesn't match source file %s",
657 hunkno, source))
658 -- file may be already patched, but check other hunks anyway
655 hunkno = hunkno + 1 659 hunkno = hunkno + 1
656 validhunks = validhunks + 1
657 if hunkno <= #hunks then 660 if hunkno <= #hunks then
658 hunk = hunks[hunkno] 661 hunk = hunks[hunkno]
662 return false
659 else 663 else
660 if validhunks == #hunks then 664 return true
661 -- patch file
662 canpatch = true
663 isbreak = true; break
664 end
665 end 665 end
666 end 666 end
667 end 667 end
668 if not isbreak then 668 -- check if processed line is the last line
669 if lineno == hunk.startsrc + #hunkfind - 1 then
670 debug(format("file %s hunk no.%d -- is ready to be patched",
671 source, hunkno))
672 hunkno = hunkno + 1
673 validhunks = validhunks + 1
669 if hunkno <= #hunks then 674 if hunkno <= #hunks then
670 warning(format("premature end of source file %s at hunk %d", 675 hunk = hunks[hunkno]
671 filename, hunkno))
672 all_ok = false
673 end
674 end
675 if validhunks < #hunks then
676 if check_patched(file, hunks) then
677 warning(format("already patched %s", filename))
678 else 676 else
679 warning(format("source file is different - %s", filename)) 677 if validhunks == #hunks then
680 all_ok = false 678 -- patch file
681 end 679 canpatch = true
682 end 680 return true
683 if canpatch then
684 local backupname = filename .. ".orig"
685 if exists(backupname) then
686 warning(format("can't backup original file to %s - aborting",
687 backupname))
688 all_ok = false
689 else
690 assert(os.rename(filename, backupname))
691 if patch_hunks(backupname, filename, hunks) then
692 warning(format("successfully patched %s", filename))
693 assert(os.remove(backupname))
694 else
695 warning(format("error patching file %s", filename))
696 assert(file_copy(filename, filename .. ".invalid"))
697 warning(format("invalid version is saved to %s",
698 filename .. ".invalid"))
699 -- todo: proper rejects
700 assert(os.rename(backupname, filename))
701 all_ok = false
702 end 681 end
703 end 682 end
704 end 683 end
684 return false
685 end
686
687 local done = false
688 for lineno, line in ipairs(file) do
689 done = process_line(line, lineno)
690 if done then
691 break
692 end
693 end
694 if not done then
695 if hunkno <= #hunks and not create_file then
696 warning(format("premature end of source file %s at hunk %d",
697 source, hunkno))
698 return false
699 end
700 end
701 if validhunks < #hunks then
702 if check_patched(file, hunks) then
703 warning(format("already patched %s", source))
704 elseif not create_file then
705 warning(format("source file is different - %s", source))
706 return false
707 end
708 end
709 if not canpatch then
710 return true
711 end
712 local backupname = source .. ".orig"
713 if exists(backupname) then
714 warning(format("can't backup original file to %s - aborting",
715 backupname))
716 return false
717 end
718 local ok = os.rename(source, backupname)
719 if not ok then
720 warning(format("failed backing up %s when patching", source))
721 return false
722 end
723 ok = patch_hunks(backupname, source, hunks)
724 if not ok then
725 warning(format("error patching file %s", source))
726 if file_copy(source, source .. ".invalid") then
727 warning(format("invalid version is saved to %s",
728 source .. ".invalid"))
729 -- todo: proper rejects
730 os.rename(backupname, source)
731 end
732 return false
733 end
734 info(format("successfully patched %s", source))
735 os.remove(backupname)
736 return true
737end
705 738
706 end -- if not continue 739function patch.apply_patch(the_patch, strip, create_delete)
707 end -- for 740 local all_ok = true
741 local total = #the_patch.source
742 for fileno, source in ipairs(the_patch.source) do
743 local target = the_patch.target[fileno]
744 local hunks = the_patch.hunks[fileno]
745 local epoch = the_patch.epoch[fileno]
746 info(format("processing %d/%d:\t %s", fileno, total, source))
747 local ok = patch_file(source, target, epoch, hunks, strip, create_delete)
748 all_ok = all_ok and ok
749 end
708 -- todo: check for premature eof 750 -- todo: check for premature eof
709 return all_ok 751 return all_ok
710end 752end