Commit d942f18e authored by jan.koester's avatar jan.koester
Browse files

diff -i support

parent 828a5212
Loading
Loading
Loading
Loading
+91 −6
Changes for commands/editor/diff.cpp: 91 added lines, 6 removed lines.
Original line number Diff line number Diff line
@@ -25,7 +25,7 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************/

// "diff [-d] [-q] FILE1 FILE2": added because rpm's own
// "diff [-d] [-q] [-I RE]... FILE1 FILE2": added because rpm's own
// /usr/local/lib/rpm/check-files (run after every package's %install)
// pipes into "diff -d "${FILES_DISK}" -" to find files present on disk
// but missing from that package's %files list -- with no diff at all,
@@ -47,6 +47,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// format. Exit status matches real diff: 0 (identical), 1 (differ), 2
// (an error, e.g. a file that can't be opened).
//
// "-I RE" (repeatable, like real diff): a changed hunk is entirely
// SUPPRESSED -- not printed, not counted toward "any differences found" --
// when EVERY line on both sides of it matches at least one of the given
// RE's (real diff's own rule: a hunk consisting of unrelated added and
// removed lines that all individually happen to match still counts as
// ignorable, not just a line-for-line identical substitution). Added
// because the kernel's own tools/objtool/sync-check.sh calls exactly this
// ("diff -I '...__ignore_sync_check__...' tools/X X") to compare a header
// against its authoritative copy while tolerating deliberately-diverged,
// specially-marked lines.
//
// The comparison itself is a classic O(N*M) dynamic-programming longest-
// common-subsequence, backtracked into a same/delete/insert edit script
// and then grouped into hunks -- not Myers' O(ND) algorithm, but more
@@ -61,6 +72,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <cerrno>
#include <cstring>

#include <regex.h>

#include "shell.h"

namespace fresh { namespace cmd {
@@ -136,13 +149,40 @@ namespace fresh { namespace cmd {
                out << (first+1) << "," << (last+1);
        }

        // RAII wrapper around a compiled "-I" pattern -- regex_t itself has
        // no destructor, regfree() must be called explicitly once, exactly
        // like grep.cpp's own regcomp()/regexec() usage.
        struct IgnorePattern {
            regex_t re;
            // regfree() on a regex_t that regcomp() never successfully
            // populated (compilation failed, or was never even attempted)
            // is undefined behavior -- POSIX explicitly leaves a failed
            // regcomp()'s output unspecified -- so this has to be tracked
            // explicitly rather than assuming every instance here was
            // successfully compiled.
            bool compiled=false;
            IgnorePattern(const IgnorePattern &)=delete;
            IgnorePattern &operator=(const IgnorePattern &)=delete;
            IgnorePattern(){}
            ~IgnorePattern(){ if(compiled) regfree(&re); }
        };

        bool matchesAny(const std::string &line,const std::vector<IgnorePattern*> &pats){
            for(auto *p:pats){
                if(regexec(&p->re,line.c_str(),0,nullptr,0)==0)
                    return true;
            }
            return false;
        }

        bool printHunks(const std::vector<std::string> &a,const std::vector<std::string> &b,
                        const std::vector<EditOp> &script){
                        const std::vector<EditOp> &script,
                        const std::vector<IgnorePattern*> &ignorePats,
                        bool quiet){
            bool anyDiff=false;
            size_t k=0;
            while(k<script.size()){
                if(script[k].op==Op::Same){ ++k; continue; }
                anyDiff=true;

                size_t delStart=k,insStart=k;
                size_t aFirst=0,aLast=0,bFirst=0,bLast=0;
@@ -161,6 +201,22 @@ namespace fresh { namespace cmd {
                }
                (void)delStart; (void)insStart;

                if(!ignorePats.empty()){
                    bool allIgnorable=true;
                    if(haveA)
                        for(size_t x=aFirst;allIgnorable && x<=aLast;++x)
                            allIgnorable=matchesAny(a[x],ignorePats);
                    if(allIgnorable && haveB)
                        for(size_t x=bFirst;allIgnorable && x<=bLast;++x)
                            allIgnorable=matchesAny(b[x],ignorePats);
                    if(allIgnorable){
                        k=j;
                        continue;
                    }
                }
                anyDiff=true;
                if(quiet){ k=j; continue; }

                if(haveA && haveB){
                    printRange(std::cout,aFirst,aLast);
                    std::cout << "c";
@@ -194,6 +250,7 @@ namespace fresh { namespace cmd {
        int runCommand(int argc,const char **argv){
            bool briefOnly=false;
            std::vector<std::string> files;
            std::vector<std::string> ignoreRes;

            for(int i=1;i<argc;++i){
                std::string a=argv[i];
@@ -202,6 +259,15 @@ namespace fresh { namespace cmd {
                    // just "a" minimal diff, so there is nothing to relax
                }else if(a=="-q" || a=="--brief"){
                    briefOnly=true;
                }else if(a=="-I"){
                    if(i+1>=argc){
                        std::cerr << "diff: -I: option requires an argument" << std::endl;
                        return 2;
                    }
                    ignoreRes.push_back(argv[++i]);
                }else if(a.rfind("-I",0)==0 && a.size()>2){
                    // Glued form ("-IRE"), same as real diff accepts.
                    ignoreRes.push_back(a.substr(2));
                }else if(!a.empty() && a[0]=='-' && a!="-"){
                    std::cerr << "diff: unsupported option: " << a << std::endl;
                    return 2;
@@ -211,10 +277,21 @@ namespace fresh { namespace cmd {
            }

            if(files.size()!=2){
                std::cerr << "diff: usage: diff [-d] [-q] FILE1 FILE2" << std::endl;
                std::cerr << "diff: usage: diff [-d] [-q] [-I RE]... FILE1 FILE2" << std::endl;
                return 2;
            }

            std::vector<IgnorePattern> ignoreStorage(ignoreRes.size());
            std::vector<IgnorePattern*> ignorePats;
            for(size_t i=0;i<ignoreRes.size();++i){
                if(regcomp(&ignoreStorage[i].re,ignoreRes[i].c_str(),REG_EXTENDED|REG_NOSUB)!=0){
                    std::cerr << "diff: -I: invalid regular expression: " << ignoreRes[i] << std::endl;
                    return 2;
                }
                ignoreStorage[i].compiled=true;
                ignorePats.push_back(&ignoreStorage[i]);
            }

            std::vector<std::string> a,b;
            if(!readLines(files[0],a)){
                std::cerr << "diff: " << files[0] << ": " << strerror(errno) << std::endl;
@@ -228,13 +305,21 @@ namespace fresh { namespace cmd {
            if(a==b)
                return 0;

            if(briefOnly){
            // With no "-I" given, a raw content difference is enough to
            // report "differ" without computing hunks at all -- same fast
            // path as before. With "-I" present, a content difference isn't
            // enough on its own: it might be entirely made up of ignorable
            // lines, so the hunk pass below has to run regardless, just
            // with its actual line output suppressed.
            if(briefOnly && ignorePats.empty()){
                std::cout << "Files " << files[0] << " and " << files[1] << " differ" << std::endl;
                return 1;
            }

            std::vector<EditOp> script=computeEditScript(a,b);
            bool anyDiff=printHunks(a,b,script);
            bool anyDiff=printHunks(a,b,script,ignorePats,briefOnly);
            if(anyDiff && briefOnly)
                std::cout << "Files " << files[0] << " and " << files[1] << " differ" << std::endl;
            return anyDiff ? 1 : 0;
        }
    };