Loading commands/editor/diff.cpp +91 −6 Changes for commands/editor/diff.cpp: 91 added lines, 6 removed lines. Original line number Diff line number Diff line Loading @@ -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, Loading @@ -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 Loading @@ -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 { Loading Loading @@ -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; Loading @@ -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"; Loading Loading @@ -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]; Loading @@ -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; Loading @@ -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; Loading @@ -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; } }; Loading Loading
commands/editor/diff.cpp +91 −6 Changes for commands/editor/diff.cpp: 91 added lines, 6 removed lines. Original line number Diff line number Diff line Loading @@ -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, Loading @@ -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 Loading @@ -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 { Loading Loading @@ -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; Loading @@ -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"; Loading Loading @@ -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]; Loading @@ -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; Loading @@ -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; Loading @@ -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; } }; Loading