diff --git a/.gitignore b/.gitignore index 528fe4b73..afc9e7141 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Files generated by the configure script .manual.xml +.revcheck.json install-unix.xml install-win.xml manual.xml diff --git a/scripts/revcheck.php b/scripts/revcheck.php index aba286215..c091775a2 100644 --- a/scripts/revcheck.php +++ b/scripts/revcheck.php @@ -20,6 +20,8 @@ +----------------------------------------------------------------------+ */ +require_once __DIR__ . '/translation/lib/all.php'; + if ( $argc != 2 ) { print <<revData; -$gitData = []; // filename lang hash - -$intro = "No intro available for the {$lang} translation of the manual."; - -$oldfiles = []; //path, name, size - -$enFiles = populateFileTree( 'en' ); -$trFiles = populateFileTree( $lang ); -captureGitValues( 'en' , $gitData ); - -computeSyncStatus( $enFiles , $trFiles , $gitData , $lang ); -$translators = computeTranslatorStatus( $lang, $enFiles, $trFiles ); - -print_html_all( $enFiles , $trFiles , $translators, $lang ); - -// Model -class OldFilesInfo -{ - public $path; - public $name; - public $size; - - public function getKey() - { - return trim( $this->path . '/' . $this->name , '/' ); - } -} - -class FileStatusEnum -{ - const Untranslated = 'Untranslated'; - const RevTagProblem = 'RevTagProblem'; - const TranslatedWip = 'TranslatedWip'; - const TranslatedOk = 'TranslatedOk'; - const TranslatedOld = 'TranslatedOld'; - const NotInEnTree = 'NotInEnTree'; -} - -class FileStatusInfo -{ - public $path; - public $name; - public $size; - public $hash; - public $skip; - public $days; - public $adds; - public $dels; - public $syncStatus; - public $maintainer; - public $completion; - public $credits; - - public function getKey() - { - return trim( $this->path . '/' . $this->name , '/' ); - } -} - -class TranslatorInfo -{ - public $name; - public $email; - public $nick; - public $vcs; - - public $files_uptodate; - public $files_outdated; - public $files_wip; - public $files_sum; - public $files_other; - - public function __construct() { - $this->files_uptodate = 0; - $this->files_outdated = 0; - $this->files_wip = 0; - $this->files_sum = 0; - $this->files_other = 0; - } - - public static function getKey( $fileStatus ) { - switch ( $fileStatus ) { - case FileStatusEnum::RevTagProblem: - case FileStatusEnum::TranslatedOld: - return "files_outdated"; - break; - case FileStatusEnum::TranslatedWip: - return "files_wip"; - break; - case FileStatusEnum::TranslatedOk: - return "files_uptodate"; - break; - default: - return "files_other"; - } - } -} - -function populateFileTree( $lang ) -{ - $dir = new \DirectoryIterator( $lang ); - if ( $dir === false ) - { - print "$lang is not a directory.\n"; - exit; - } - $cwd = getcwd(); - $ret = array(); - chdir( $lang ); - populateFileTreeRecurse( $lang , "." , $ret ); - chdir( $cwd ); - return $ret; -} - -function populateFileTreeRecurse( $lang , $path , & $output ) -{ - global $oldfiles; - $dir = new DirectoryIterator( $path ); - if ( $dir === false ) - { - print "$path is not a directory.\n"; - exit; - } - $todoPaths = []; - $trimPath = ltrim( $path , "./"); - foreach( $dir as $entry ) - { - $filename = $entry->getFilename(); - if ( $filename[0] == '.' ) - continue; - if ( substr( $filename , 0 , 9 ) == "entities." ) - continue; - if ( $entry->isDir() ) - { - $todoPaths[] = $path . '/' . $entry->getFilename(); - continue; - } - if ( $entry->isFile() ) - { - $ignoredFileNames = [ - 'README.md', - 'translation.xml', - 'readme.first', - 'license.xml', - 'extensions.xml', - 'versions.xml', - 'book.developer.xml', - 'contributors.ent', - 'contributors.xml', - 'README', - 'DO_NOT_TRANSLATE', - 'rsusi.txt', - 'missing-ids.xml', - ]; - - $ignoredDirectories = [ - 'chmonly', - ]; - - $ignoredFullPaths = [ - 'appendices/reserved.constants.xml', - 'appendices/extensions.xml', - 'reference/datetime/timezones.xml', - ]; - - if( - in_array($trimPath, $ignoredDirectories, true) - || in_array($filename, $ignoredFileNames, true) - || (strpos($filename, 'entities.') === 0) - || !in_array(substr($filename, -3), ['xml','ent'], true) - || (substr($filename, -13) === 'PHPEditBackup') - || (in_array($trimPath . '/' .$filename, $ignoredFullPaths, true)) - ) { - continue; - } - $file = new FileStatusInfo; - $file->path = $trimPath; - $file->name = $filename; - $file->size = filesize( $path . '/' . $filename ); - $file->syncStatus = null; - if ( $lang != 'en' ) - { - parseRevisionTag( $entry->getPathname() , $file ); - $path_en = '../en/' . $trimPath . '/' . $filename; - if( !is_file($path_en) ) //notinen - { - $oldfile = new OldFilesInfo; - $oldfile->path = $trimPath; - $oldfile->name = $filename; - $oldfile->size = $file->size < 1024 ? 1 : floor( $file->size / 1024 ); - $oldfiles[ $oldfile->getKey() ] = $oldfile; - } else { - $output[ $file->getKey() ] = $file; - } - } else { - $output[ $file->getKey() ] = $file; - } - } - } - sort( $todoPaths ); - foreach( $todoPaths as $path ) - populateFileTreeRecurse( $lang , $path , $output ); -} - -function parseRevisionTag( $filename , FileStatusInfo $file ) -{ - $fp = fopen( $filename , "r" ); - $contents = fread( $fp , 1024 ); - fclose( $fp ); - - // No match before the preg - $match = array (); - - $regex = "''U"; - if (preg_match ($regex , $contents , $match )) { - $file->hash = trim( $match[1] ); - $file->maintainer = trim( $match[2] ); - $file->completion = trim( $match[3] ); - } - if ( $file->hash == null or strlen( $file->hash ) != 40 or - $file->maintainer == null or - $file->completion == null ) - $file->syncStatus = FileStatusEnum::RevTagProblem; - - $regex = "//U"; - $match = array(); - preg_match ( $regex , $contents , $match ); - if ( count( $match ) == 2 ) - $file->credits = str_replace( ' ' , '' , trim( $match[1] ) ); - else - $file->credits = ''; -} - -function captureGitValues( $lang , & $output ) -{ - $cwd = getcwd(); - chdir( $lang ); - $fp = popen( "git --no-pager log --name-only" , "r" ); - $hash = $additions = $deletions = $filename = null; - $skip = false; - while ( ( $line = fgets( $fp ) ) !== false ) - { - if ( substr( $line , 0 , 7 ) == "commit " ) - { - $hash = trim( substr( $line , 7 ) ); - $skip = false; - continue; - } - if ( strpos( $line , 'Date:' ) === 0 ) - continue; - if ( trim( $line ) == "" ) - continue; - if ( substr( $line , 0 , 4 ) == ' ' ) - { - if ( stristr( $line, '[skip-revcheck]' ) !== false ) - $skip = true; - continue; - } - if ( strpos( $line , ': ' ) > 0 ) - continue; - $filename = trim( $line ); - if ( isset( $output[$filename][$lang] ) ) - continue; - - $output[$filename][$lang]['hash'] = $hash; - $output[$filename][$lang]['skip'] = $skip; - } - pclose( $fp ); - chdir( $cwd ); -} - -function computeSyncStatus( $enFiles , $trFiles , $gitData , $lang ) -{ - foreach( $trFiles as $filename => $trFile ) - { - // notinen - $path_en = 'en/' . $trFile->path . '/' . $trFile->name; - if( !is_file($path_en) ) - { - $trFile->syncStatus = FileStatusEnum::NotInEnTree; - continue; - } - - } - foreach( $enFiles as $filename => $enFile ) - { - if ( isset( $gitData[ $filename ]['en'] ) ) - { - $enFile->hash = $gitData[ $filename ]['en']['hash']; - $enFile->skip = $gitData[ $filename ]['en']['skip']; - } - else - print "Warn: No hash for en/$filename
"; - - $trFile = isset( $trFiles[ $filename ] ) ? $trFiles[ $filename ] : null; - - if ( $trFile == null ) // Untranslated - { - $enFile->syncStatus = FileStatusEnum::Untranslated; - continue; - } - if ( $trFile->syncStatus == FileStatusEnum::RevTagProblem ) - continue; - - // TranslatedOk - // TranslatedOld - if ( strlen( $trFile->hash ) == 40 ) - { - if ( $enFile->hash == $trFile->hash ) - $trFile->syncStatus = FileStatusEnum::TranslatedOk; - else - { - $trFile->syncStatus = FileStatusEnum::TranslatedOld; - - $cwd = getcwd(); - chdir( 'en' ); - //adds,dels - $subject = `git diff --numstat $trFile->hash -- {$filename}`; - if ( $subject ) - { - preg_match('/(\d+)\s+(\d+)/', $subject, $matches); - if ($matches) - [, $enFile->adds, $enFile->dels] = $matches; - } - //days - $days = `git show --no-patch --format='%ct' $enFile->hash -- {$filename}`; - if ( $days != "" ) - $enFile->days = floor( ( time() - $days ) / 86400 ); - chdir( $cwd ); - - if ( $enFile->skip ) - { - $cwd = getcwd(); - chdir( 'en' ); - $hashes = explode ( "\n" , `git log -2 --format=%H -- {$filename}` ); - chdir( $cwd ); - if ( $hashes[1] == $trFile->hash ) - $trFile->syncStatus = FileStatusEnum::TranslatedOk; - } - } - } - // TranslatedWip - if ( $trFile->completion != null && $trFile->completion != "ready" ) - $trFile->syncStatus = FileStatusEnum::TranslatedWip; - } -} - -function parse_attr_string ( $tags_attrs ) { - $tag_attrs_processed = array(); - - foreach($tags_attrs as $attrib_list) { - preg_match_all("!(.+)=\\s*([\"'])\\s*(.+)\\2!U", $attrib_list, $attribs); - - $attrib_array = array(); - foreach ($attribs[1] as $num => $attrname) { - $attrib_array[trim($attrname)] = trim($attribs[3][$num]); - } - - $tag_attrs_processed[] = $attrib_array; - } - - return $tag_attrs_processed; -} - -function computeTranslatorStatus( $lang, $enFiles, $trFiles ) { - global $intro; - $translation_xml = getcwd() . "/" . $lang . "/translation.xml"; - if (!file_exists($translation_xml)) { - return []; - } - - $txml = join("", file($translation_xml)); - $txml = preg_replace("/\\s+/", " ", $txml); - - preg_match("!(.+)!s", $txml, $match); - $intro = trim($match[1]); - - preg_match("!<\?xml(.+)\?>!U", $txml, $match); - $xmlinfo = parse_attr_string($match); - $output_charset = $xmlinfo[1]["encoding"]; - - $pattern = "!!U"; - preg_match_all($pattern, $txml, $matches); - $translators = parse_attr_string($matches[1]); - - $translatorInfos = []; - $unknownInfo = new TranslatorInfo(); - $unknownInfo->nick = "unknown"; - $translatorInfos["unknown"] = $unknownInfo; - - foreach ($translators as $key => $translator) { - $info = new TranslatorInfo(); - $info->name = $translator["name"]; - $info->email = $translator["email"]; - $info->nick = $translator["nick"]; - $info->vcs = $translator["vcs"] ?? ""; - - $translatorInfos[$info->nick] = $info; - } - - foreach( $enFiles as $key => $enFile ) { - $info_exists = false; - if (array_key_exists($enFile->getKey(), $trFiles)) { - $trFile = $trFiles[$enFile->getKey()]; - $statusKey = TranslatorInfo::getKey($trFile->syncStatus); - if (array_key_exists($trFile->maintainer, $translatorInfos)) { - $translatorInfos[$trFile->maintainer]->$statusKey++; - $translatorInfos[$trFile->maintainer]->files_sum++; - $info_exists = true; - } - } - if (!$info_exists) { - $translatorInfos["unknown"]->$statusKey++; - $translatorInfos["unknown"]->files_sum++; - } - } - - return $translatorInfos; -} +print_html_all( $data ); // Output -function print_html_all( $enFiles , $trFiles , $translators , $lang ) +function print_html_all( RevcheckData $data ) { - print_html_header( $lang ); - print_html_translators($translators , $enFiles, $trFiles); - print_html_files( $enFiles , $trFiles , $lang ); - print_html_notinen(); - print_html_misstags( $enFiles, $trFiles, $lang ); - print_html_untranslated( $enFiles ); + print_html_header( $data ); + print_html_translators( $data ); + print_html_oldwip( $data ); + print_html_notinen( $data ); + print_html_revtag( $data ); + print_html_untranslated( $data ); print_html_footer(); } -function print_html_header( $lang ) +function print_html_header( RevcheckData $data ) { - $date = date("r"); + $lang = $data->lang; + $date = $data->date; print << @@ -512,35 +94,34 @@ function print_html_header( $lang )

Status of the translated PHP Manual

Generated: $date / Language: $lang

- HTML; } - -function print_html_menu($href) +function print_html_menu( string $href ) { print <<

Introduction | Translators | File summary | Outdated Files | Not in EN tree -| Missing revision numbers +| Missing or invalid revtag | Untranslated files

HTML; } -function print_html_translators( $translators , $enFiles, $trFiles ) +function print_html_translators( RevcheckData $data ) { - global $intro, $oldfiles, $files_misstags, $notinen_count, $files_untranslated; - if (count($translators) === 0) return; + $translators = $data->translators; + if ( count( $translators ) == 0 ) + return; + print_html_menu("intro"); print << - $intro + {$data->intro}

@@ -552,83 +133,36 @@ function print_html_translators( $translators , $enFiles, $trFiles ) - + - + HTML; - $files_uptodate = 0; - $files_outdated = 0; - $files_wip = 0; - $files_sum = 0; - foreach( $translators as $key => $person ) + foreach( $translators as $person ) { - if ($person->nick === "unknown") continue; + // Unknown or untracked on translations.xml + if ( $person->name == "" && $person->email == "" && $person->vcs == "" ) + continue; - $files_uptodate += $person->files_uptodate; - $files_outdated += $person->files_outdated; - $files_wip += $person->files_wip; - $files_sum += $person->files_sum; - print <<countOk + $person->countOld + $person->countOther; + print << - - - - - + + + + - HTML; - } print "
Files maintained
upto-
date
upto-
date
oldwipwip sum
{$person->name} {$person->email} {$person->nick} {$person->vcs}{$person->files_uptodate}{$person->files_outdated}{$person->files_wip}{$person->files_sum}{$person->countOk}{$person->countOld}{$person->countOther}{$personSum}
\n"; -//FILE SUMMARY - $count = 0; - $files_outdated = 0; - $files_sum = 0; - $files_uptodate = 0; - $files_misstags = 0; - $files_wip = 0; - foreach( $trFiles as $key => $tr ) - { - if ( $tr->syncStatus == FileStatusEnum::TranslatedOld ) - $files_outdated++; - if ( $tr->syncStatus == FileStatusEnum::TranslatedOk ) - $files_uptodate++; - if ( $tr->syncStatus == FileStatusEnum::RevTagProblem ) - $files_misstags++; - if ( $tr->syncStatus == FileStatusEnum::TranslatedWip ) - $files_wip++; - } - $files_untranslated = 0; - foreach( $enFiles as $key => $en ) - { - if ( $en->syncStatus == FileStatusEnum::Untranslated ) { - $files_untranslated++; - } - $count++; - } - $notinen_count = 0; - foreach( $oldfiles as $key => $en ) - { - if ( $key == "{$en->path}/{$en->name}" ) { - $notinen_count++; - } - } - $files_uptodate_percent = number_format($files_uptodate * 100 / $count, 2 ); - $files_outdated_percent = number_format($files_outdated * 100 / $count, 2 ); - $files_wip_percent = number_format($files_wip * 100 / $count, 2 ); - $files_untranslated_percent = number_format($files_untranslated * 100 / $count, 2 ); - $notinen_count_percent = number_format($notinen_count * 100 / $count, 2 ); - $files_misstags_percent = number_format($files_misstags * 100 / $count, 2 ); print_html_menu("filesummary"); print << @@ -637,150 +171,59 @@ function print_html_translators( $translators , $enFiles, $trFiles ) Number of files Percent of files - - Up to date files - $files_uptodate - $files_uptodate_percent% - - - Outdated files - $files_outdated - $files_outdated_percent% - - - Work in progress - $files_wip - $files_wip_percent% - - - Files without revision number - $files_misstags - $files_misstags_percent% - - - Not in EN tree - $notinen_count - $notinen_count_percent% - - - Files available for translation - $files_untranslated - $files_untranslated_percent% - - - Files total - $count - 100% -

HTML; -} -function print_html_misstags( $enFiles, $trFiles, $lang ) -{ - print_html_menu("misstags"); + $filesTotal = 0; + foreach ( $data->fileSummary as $count ) + $filesTotal += $count; - GLOBAL $files_misstags; - if ($files_misstags == 0) + foreach( RevcheckStatus::cases() as $key ) { - echo '

Good, all files contain revision numbers.

'; - } else { + $label = ""; + $count = $data->fileSummary[ $key->value ]; + $perc = number_format( $count / $filesTotal * 100 , 2 ) . "%"; + switch( $key ) + { + case RevcheckStatus::TranslatedOk: $label = "Up to date files"; break; + case RevcheckStatus::TranslatedOld: $label = "Outdated files"; break; + case RevcheckStatus::TranslatedWip: $label = "Work in progress"; break; + case RevcheckStatus::RevTagProblem: $label = "Revision tag missing/problem"; break; + case RevcheckStatus::NotInEnTree: $label = "Not in EN tree"; break; + case RevcheckStatus::Untranslated: $label = "Available for translation"; break; + } + print << - Files without EN-Revision number ($files_misstags files) - Commit hash - Sizes in kB + $label + $count + $perc -en$langdiff HTML; - - $last_path = null; - asort($trFiles); - foreach ($trFiles as $key => $tr) - { - if ( $tr->syncStatus != FileStatusEnum::RevTagProblem ) - continue; - - $en = $enFiles[ $key ]; - - if ( $last_path != $tr->path ) - { - $path = $tr->path == '' ? '/' : $tr->path; - echo "$path"; - $last_path = $tr->path; - } - $diff = intval($en->size - $tr->size); - echo "{$tr->name}{$en->hash}{$en->size}{$tr->size}$diff"; - } - echo ''; } -} -function print_html_untranslated($enFiles) -{ - global $files_untranslated; - $exists = false; - if (!$files_untranslated) return; - print_html_menu("untranslated"); print << - - Untranslated files ($files_untranslated files): - Commit hash - kb - + + Files total + $filesTotal + 100% + + HTML; +} - $path = null; - asort($enFiles); - foreach( $enFiles as $key => $en ) - { - if ( $en->syncStatus != FileStatusEnum::Untranslated ) - continue; - if ( $path !== $en->path ) - { - $path = $en->path; - $path2 = $path == '' ? '/' : $path; - print " $path2"; - } - $size = $en->size < 1024 ? 1 : floor( $en->size / 1024 ); - - print << - $en->name - $en->hash - $size - -HTML; + $total = $data->fileSummary[ RevcheckStatus::TranslatedOld->value ]; + $total += $data->fileSummary[ RevcheckStatus::TranslatedWip->value ]; + if ( $total == 0 ) + { + print "

Hooray! There is no files to update, nice work!

\n\n"; + return; } - print "\n"; -} -function print_html_footer() -{ - print_html_menu(""); print << - - - - -HTML; -} - -function print_html_files( $enFiles , $trFiles , $lang ) -{ - print_html_menu("files"); - print << Translated file @@ -792,101 +235,217 @@ function print_html_files( $enFiles , $trFiles , $lang ) en - $lang - - + {$data->lang} + \n HTML; $now = new DateTime( 'now' ); $path = null; - asort($trFiles); - foreach( $trFiles as $key => $tr ) + + foreach( $data->fileDetail as $key => $file ) { - if ( $tr->syncStatus == FileStatusEnum::TranslatedOk ) - continue; - if ( $tr->syncStatus == FileStatusEnum::RevTagProblem ) - continue; - if ( $tr->syncStatus == FileStatusEnum::NotInEnTree ) - continue; - $en = $enFiles[ $key ]; - if ( $en->syncStatus == FileStatusEnum::Untranslated ) - continue; + switch ( $file->status ) + { + case RevcheckStatus::TranslatedOld: + case RevcheckStatus::TranslatedWip: + break; + default: + continue 2; + } - if ( $path !== $en->path ) + if ( $path !== $file->path ) { - $path = $en->path; + $path = $file->path; $path2 = $path == '' ? '/' : $path; print " $path2"; } - $ll = strtolower( $lang ); + + $ma = $file->maintainer; + $st = $file->completion; + $ll = strtolower( $data->lang ); $kh = hash( 'sha256' , $key ); - $d1 = "https://doc.php.net/revcheck.php?p=plain&lang={$ll}&hbp={$tr->hash}&f=$key&c=on"; - $d2 = "https://doc.php.net/revcheck.php?p=plain&lang={$ll}&hbp={$tr->hash}&f=$key&c=off"; - $nm = "{$en->name} [colored]"; - if ( $en->syncStatus == FileStatusEnum::RevTagProblem ) - $nm = $en->name; - $h1 = "{$en->hash}"; - $h2 = "{$tr->hash}"; + $d1 = "https://doc.php.net/revcheck.php?p=plain&lang={$ll}&hbp={$file->hashRvtg}&f=$key"; + $d2 = "https://doc.php.net/revcheck.php?p=plain&lang={$ll}&hbp={$file->hashRvtg}&f=$key&c=on"; - $bgdays = ''; - if ($en->days != null && $en->days > 90) - $bgdays = 'bgorange'; + $nm = "{$file->name} [colored]"; + $h1 = "{$file->hashLast}"; + $h2 = "{$file->hashRvtg}"; - if ($en->adds != null) - $ch = "+{$en->adds} -{$en->dels}"; + if ( $file->adds > 0 || $file->dels > 0 ) + $ch = "+{$file->adds} -{$file->dels}"; else - $ch = "no data"; + $ch = ""; + + $bgdays = ''; + if ( $file->days > 90 ) + $bgdays = 'bgorange'; - $ma = $tr->maintainer; - $st = $tr->completion; print << $nm $ch - $h1 + $h1 $h2 $ma $st - {$en->days} - + {$file->days} + \n HTML; } -print "

\n"; + + print "

\n\n"; } -function print_html_notinen() +function print_html_notinen( RevcheckData $data ) { - global $oldfiles, $notinen_count; print_html_menu("notinen"); - $exists = false; - if (!$notinen_count) + + if ( $data->fileSummary[ RevcheckStatus::NotInEnTree->value ] == 0 ) { - print "

Good, it seems that this translation doesn't contain any file which is not present in English tree.

\n"; - } else { - print <<Good, it seems that this translation doesn't contain any file which is not present in source tree.

\n\n"; + return; + } + + print << - Files which is not present in English tree. ($notinen_count files) - Size in kB + Files which is not present in source tree + Size kB HTML; - $path = null; - foreach( $oldfiles as $key => $en ) - { - if ( $path !== $en->path ) - { - $path = $en->path; - print " /$path"; - } - print <<fileDetail as $file ) + { + if ( $file->status != RevcheckStatus::NotInEnTree ) + continue; + + if ( $header !== $file->path ) + { + $header = $file->path; + print " $header"; + } + + $name = $file->name; + $size = round( $file->size / 1024 ); + + print << - $en->name - $en->size + {$name} + {$size} + +HTML; + } + + print "

\n\n"; +} + +function print_html_revtag( RevcheckData $data ) +{ + print_html_menu("revtag"); + if ( $data->fileSummary[ RevcheckStatus::RevTagProblem->value ] == 0 ) + { + echo "

Good, all files contain valid revtags.

\n\n"; + return; + } + + echo << + + Files with invalid or missing revision tags + Size kB + +HTML; + + $last_path = null; + foreach ( $data->fileDetail as $file ) + { + if ( $file->status != RevcheckStatus::RevTagProblem ) + continue; + + if ( $last_path != $file->path ) + { + $path = $file->path == '' ? '/' : $file->path; + echo "$path"; + $last_path = $file->path; + } + $size = round( $file->size / 1024 ); + echo "{$file->name}{$size}"; + } + echo ''; +} + +function print_html_untranslated( RevcheckData $data ) +{ + print_html_menu("untranslated"); + if ( $data->fileSummary[ RevcheckStatus::Untranslated->value ] == 0 ) + { + echo "

No file left untranslated!

\n\n"; + return; + } + + print << + + Untranslated files + Last hash + kb + +HTML; + + $path = null; + foreach ( $data->fileDetail as $key => $file ) + { + if ( $file->status != RevcheckStatus::Untranslated ) + continue; + + if ( $path !== $file->path ) + { + $path = $file->path; + $header = $path == '' ? '/' : $path; + print " $header"; + } + + $name = $file->name; + $hash = $file->hashLast; + $href = "https://github.com/php/doc-en/blob/{$hash}/$key"; + $size = round( $file->size / 1024 ); + + print << + $name + $hash + $size HTML; - } -print "

"; } + print "\n\n"; +} + +function print_html_footer() +{ + print_html_menu(""); + print << + + + + +HTML; } + +function print_debug_list( RevcheckData $data ) +{ + foreach( $data->fileDetail as $key => $file ) + print "f:$key m:{$file->maintainer} s:{$file->status->value}\n"; + die(); +} \ No newline at end of file diff --git a/scripts/translation/genrevdb.php b/scripts/translation/genrevdb.php new file mode 100644 index 000000000..aa87650a6 --- /dev/null +++ b/scripts/translation/genrevdb.php @@ -0,0 +1,251 @@ + | + * +----------------------------------------------------------------------+ + * | Description: Check format for revtags and credits on XML comments. | + * +----------------------------------------------------------------------+ + */ + +require_once __DIR__ . '/lib/all.php'; + +if ( count( $argv ) < 3 || in_array( '--help' , $argv ) || in_array( '-h' , $argv ) ) +{ + fwrite( STDERR , "\nUsage: {$argv[0]} [file.db] [lang1,langN]\n\n" ); + return; +} + +$timeStart = new \DateTime; +$dbpath = $argv[1]; +$langs = array(); +for( $idx = 2 ; $idx < count( $argv ) ; $idx++ ) + $langs[] = $argv[ $idx ]; + +consolelog( "Creating revdata database $dbpath for languages: " . implode( ',', $langs ) . '.'); + +$db = db_create( $dbpath ); +foreach( $langs as $lang ) + generate( $db , $lang ); + +consolelog( "Revdata database $dbpath complete." ); +exit; + + + +function generate( SQLite3 $db , string $lang ) +{ + $cwd = getcwd(); + if ( ! is_dir( $lang ) ) + { + consolelog( "Error: '$cwd/$lang' doesn't exist. Skipped." ); + return; + } + if ( ! is_file( "$lang/translation.xml" ) ) + { + consolelog( "Error: '$cwd/$lang' doesn't contains translation.xml. Skipped." ); + return; + } + + try + { + consolelog( "Language $lang started." ); + + $revcheck = new RevcheckRun( 'en' , $lang ); + $data = $revcheck->revData; + + $db->exec( 'BEGIN TRANSACTION' ); + + db_insert( $db , "languages" , $data->lang , $data->intro ); + + foreach( $data->translators as $translator ) + if ( $translator->nick != "" ) + db_insert( $db , "translators", $data->lang + , $translator->name + , $translator->nick + , $translator->email + , $translator->vcs + , $translator->countOk + , $translator->countOld + , $translator->countOther + ); + + foreach( $data->fileDetail as $file ) + db_insert( $db , "files", $data->lang + , $file->path + , $file->name + , $file->size + , $file->days + , $file->adds + , $file->dels + , $file->status->value + , $file->maintainer + , $file->completion + , $file->hashLast + , $file->hashDiff + , $file->hashRvtg + ); + + $filesTotal = 0; + foreach( $data->fileSummary as $count ) + $filesTotal += $count; + $labels = $data->getSummaryLabels(); + foreach( $data->fileSummary as $status => $count ) + db_insert( $db , "summary", $data->lang + , $status + , $labels[ $status ] + , $count + , number_format( $count / $filesTotal * 100 , 2 ) . "%" + ); + + $db->exec( 'COMMIT TRANSACTION' ); + consolelog_timed( "Language $lang finished." ); + } + catch ( Exception $e ) + { + $db->exec( 'ROLLBACK TRANSACTION' ); + consolelog( "Throw: " . $e->getMessage() ); + exit; + } +} + +function db_insert( SQLite3 $db , string $table , ... $values ) : void +{ + $dml = "INSERT INTO $table VALUES ("; + $cmm = ""; + foreach( $values as $v ) + { + $dml .= "$cmm?"; + $cmm = ","; + } + $dml .= ");\n"; + + $cmd = $db->prepare( $dml ); + if ( ! $cmd ) + { + consolelog_error( "Error: Prepare failed." , $db ); + throw new \Exception; + } + + $idx = 0; + foreach( $values as $val ) + { + $idx++; + $cmd->bindValue( $idx , $val ); + } + + $sql = $cmd->getSQL( true ); + + $res = $cmd->execute(); + if ( ! $res ) + { + consolelog_error( "Error: '$sql'" , $db ); + throw new \Exception; + } +} + +function db_create( $path ) : SQLite3 +{ + if ( is_file ( $path ) ) + { + consolelog( "Previous database file found, deleting." ); + if ( ! @ unlink ( $path ) ) + { + consolelog( "Error: Can't remove temporary database." ); + exit; + } + } + + $ddl = <<exec( $ddl ) ) + { + consolelog_error( "Error: Database creation failed." , $db ); + exit; + } + return $db; + } + catch ( Exception $e ) + { + consolelog( "Throw: " . $e->getMessage() ); + exit; + } +} + +function consolelog( $message ) : void +{ + $time = (new \DateTime())->format('Y-m-d H:i'); + echo "[$time] $message\n"; +} + +function consolelog_timed( $message ) : void +{ + static $lastMark = null; + if ( $lastMark == null ) + { + global $timeStart; + $lastMark = $timeStart; + } + $seconds = (new \DateTime)->getTimestamp() - $lastMark->getTimestamp(); + $lastMark = new \DateTime; + $time = $lastMark->format('Y-m-d H:i'); + echo sprintf( "[%s] %s (elapsed %.02fs)\n", $time, $message, $seconds ); +} + +function consolelog_error( string $message , SQLite3 $db ) : void +{ + consolelog( $message ); + consolelog( 'SQLite3: (' . $db->lastErrorCode() . ') ' . $db->lastErrorMsg() ); +} diff --git a/scripts/translation/lib/GitDiffParser.php b/scripts/translation/lib/GitDiffParser.php index 9df98ac07..169c13c16 100644 --- a/scripts/translation/lib/GitDiffParser.php +++ b/scripts/translation/lib/GitDiffParser.php @@ -21,6 +21,28 @@ class GitDiffParser { - public static function parseNumstatInto( string $dir , RevcheckFileInfo $file ) - {} + public static function parseAddsDels( string $chdir , RevcheckDataFile $file ) + { + $cwd = getcwd(); + chdir( $chdir ); + + $hash = $file->hashRvtg; + $name = $file->path == "" ? $file->name : $file->path . "/" . $file->name; + + $hash = escapeshellarg( $hash ); + $name = escapeshellarg( $name ); + + $output = `git diff --numstat $hash -- $name`; + if ( $output ) + { + preg_match( '/(\d+)\s+(\d+)/' , $output , $matches ); + if ( $matches ) + { + $file->adds = $matches[1]; + $file->dels = $matches[2]; + } + } + + chdir( $cwd ); + } } diff --git a/scripts/translation/lib/GitLogParser.php b/scripts/translation/lib/GitLogParser.php index 151d18456..65095f90c 100644 --- a/scripts/translation/lib/GitLogParser.php +++ b/scripts/translation/lib/GitLogParser.php @@ -29,6 +29,7 @@ static function parseInto( string $lang , RevcheckFileList & $list ) $hash = ""; $date = ""; $skip = false; + $mcnt = 0; while ( ( $line = fgets( $fp ) ) !== false ) { // new commit block @@ -37,6 +38,7 @@ static function parseInto( string $lang , RevcheckFileList & $list ) $hash = trim( substr( $line , 7 ) ); $date = ""; $skip = false; + $mcnt = 0; continue; } // datetime of commit @@ -46,20 +48,31 @@ static function parseInto( string $lang , RevcheckFileList & $list ) $date = strtotime( $line ); continue; } - // other headers - if ( strpos( $line , ': ' ) > 0 ) - continue; // empty lines if ( trim( $line ) == "" ) continue; // commit message if ( str_starts_with( $line , ' ' ) ) { - // commits with this mark are ignored - if ( stristr( $line, '[skip-revcheck]' ) !== false ) - $skip = true; + if ( LOOSE_SKIP_REVCHECK ) // See below, and https://github.com/php/doc-base/pull/132 + { + // commits with [skip-revcheck] anywhere commit message flags skip + if ( str_contains( $line, '[skip-revcheck]' ) ) + $skip = true; + } + else + { + $mcnt++; + // [skip-revcheck] at start of first line of commit message flags a skip + if ( $mcnt == 1 && str_starts_with( trim( $line ) , '[skip-revcheck]' ) ) + $skip = true; + } continue; } + // other headers + if ( strpos( $line , ': ' ) > 0 ) + continue; + // otherwise, a filename $filename = trim( $line ); $info = $list->get( $filename ); @@ -68,22 +81,34 @@ static function parseInto( string $lang , RevcheckFileList & $list ) if ( $info == null ) continue; - // the head commit + // Saves only the first commit hash of a file of git log, + // that is, the last commit hash in chronological order. + if ( $info->head == "" ) { $info->head = $hash; $info->date = $date; - } - // after, only tracks non skipped commits - if ( $skip ) - continue; + if ( FIXED_SKIP_REVCHECK ) + if ( $skip ) + $info->diff = "skip"; + } - // the diff commit - if ( $info->diff == "" ) + if ( !FIXED_SKIP_REVCHECK ) { - $info->diff = $hash; - $info->date = $date; + // Also tracks the first commit hash of a file in git log + // that is *not* market with [skip-revcheck] (the diff hash) + // so it's possible to not bother translations with + // minutiae modifications. + + if ( $skip ) + continue; + + if ( $info->diff == "" ) + { + $info->diff = $hash; + $info->date = $date; + } } } diff --git a/scripts/translation/lib/RevcheckData.php b/scripts/translation/lib/RevcheckData.php new file mode 100644 index 000000000..d3e19b977 --- /dev/null +++ b/scripts/translation/lib/RevcheckData.php @@ -0,0 +1,106 @@ + | + * +----------------------------------------------------------------------+ + * | Description: DTO for serialization of revcheck data. | + * +----------------------------------------------------------------------+ + */ + +// NOTE: This file MAY be used in more of one git repository in future. +// If it is the case, please make note of this in *both* places. + +enum RevcheckStatus : string +{ + case TranslatedOk = 'TranslatedOk'; + case TranslatedOld = 'TranslatedOld'; + case TranslatedWip = 'TranslatedWip'; + case RevTagProblem = 'RevTagProblem'; + case NotInEnTree = 'NotInEnTree'; + case Untranslated = 'Untranslated'; +} + +class RevcheckData +{ + public string $lang = ""; + public string $date = ""; + public string $intro = ""; + public $translators = array(); // nick => RevcheckDataTranslator + public $fileSummary = array(); // RevcheckStatus => int + public $fileDetail = array(); // filename => RevcheckDataFile + + public function __construct() + { + foreach ( RevcheckStatus::cases() as $status ) + $this->fileSummary[ $status->value ] = 0; + } + + public function addFile( string $key , RevcheckDataFile $file ) + { + $this->fileDetail[ $key ] = $file; + $this->fileSummary[ $file->status->value ]++; + } + + public function getTranslator( string $nick ) + { + $translator = $this->translators[ $nick ] ?? null; + if ( $translator == null ) + { + $translator = new RevcheckDataTranslator(); + $translator->nick = $nick; + $this->translators[ $nick ] = $translator; + } + return $translator; + } + + public function getSummaryLabels() : array + { + $ret[ RevcheckStatus::TranslatedOk->value ] = "Up to date files"; + $ret[ RevcheckStatus::TranslatedOld->value ] = "Outdated files"; + $ret[ RevcheckStatus::TranslatedWip->value ] = "Work in progress"; + $ret[ RevcheckStatus::RevTagProblem->value ] = "Revision tag missing/problem"; + $ret[ RevcheckStatus::NotInEnTree->value ] = "Not in EN tree"; + $ret[ RevcheckStatus::Untranslated->value ] = "Available for translation"; + return $ret; + } +} + +class RevcheckDataTranslator +{ + public string $name = ""; + public string $email = ""; + public string $nick = ""; + public string $vcs = ""; + + public int $countOk = 0; + public int $countOld = 0; + public int $countOther = 0; +} + +class RevcheckDataFile +{ + public string $path; + public string $name; + public int $size; + public int $days; + public int $adds = 0; + public int $dels = 0; + + public RevcheckStatus $status; + public string $maintainer = ""; + public string $completion = ""; + + public string $hashLast; // The most recent commit hash, skipped or not + public string $hashDiff; // The most recent, non [skip-revcheck] commit hash + public string $hashRvtg = ""; // Revtag hash, if any +} diff --git a/scripts/translation/lib/RevcheckFileInfo.php b/scripts/translation/lib/RevcheckFileInfo.php index 027c93fd8..5ebc5c46e 100644 --- a/scripts/translation/lib/RevcheckFileInfo.php +++ b/scripts/translation/lib/RevcheckFileInfo.php @@ -19,16 +19,6 @@ require_once __DIR__ . '/all.php'; - enum RevcheckStatus :string -{ - case Untranslated = 'Untranslated'; - case RevTagProblem = 'RevTagProblem'; - case TranslatedOk = 'TranslatedOk'; - case TranslatedOld = 'TranslatedOld'; - case TranslatedWip = 'TranslatedWip'; - case NotInEnTree = 'NotInEnTree'; -} - class RevcheckFileInfo { public string $file = ""; // from fs diff --git a/scripts/translation/lib/RevcheckIgnore.php b/scripts/translation/lib/RevcheckIgnore.php index a2a36d026..bb37eabda 100644 --- a/scripts/translation/lib/RevcheckIgnore.php +++ b/scripts/translation/lib/RevcheckIgnore.php @@ -42,7 +42,7 @@ public static function ignore( $filename ) : bool if ( str_contains( $filename , "/versions.xml" ) ) return true; - // Only in English + // Only in English, autogenerated, marked not translatable if ( $filename == "contributors.ent" ) return true; @@ -50,18 +50,19 @@ public static function ignore( $filename ) : bool return true; if ( $filename == "appendices/license.xml" ) return true; - if ( $filename == "appendices/license.xml" ) - return true; if ( $filename == "appendices/extensions.xml" ) return true; if ( $filename == "appendices/reserved.constants.xml" ) return true; if ( $filename == "reference/datetime/timezones.xml" ) return true; - if ( str_starts_with( $filename , 'chmonly/') ) - return true; - if ( str_ends_with( $filename , '/book.developer.xml') ) - return true; + + if ( IGNORE_EXTENSIONS_XML ) + if ( str_ends_with( $filename , '/extensions.xml') ) // track/count backport + return true; + if ( IGNORE_CHMONLY_DIR ) + if ( str_starts_with( $filename , 'chmonly/') ) // track/count backport + return true; // Only in translations diff --git a/scripts/translation/lib/RevcheckRun.php b/scripts/translation/lib/RevcheckRun.php index 7b2cd4e66..b9164b2ee 100644 --- a/scripts/translation/lib/RevcheckRun.php +++ b/scripts/translation/lib/RevcheckRun.php @@ -27,16 +27,17 @@ class RevcheckRun public RevcheckFileList $sourceFiles; public RevcheckFileList $targetFiles; - // Separated lists public array $filesOk = []; public array $filesOld = []; public array $filesRevtagProblem = []; public array $filesUntranslated = []; public array $filesNotInEn = []; public array $filesWip = []; + public array $qaList = []; + public RevcheckData $revData; - function __construct( string $sourceDir , string $targetDir , bool $writeResults = true ) + function __construct( string $sourceDir , string $targetDir , bool $writeResults = false ) { $this->sourceDir = $sourceDir; $this->targetDir = $targetDir; @@ -52,16 +53,22 @@ function __construct( string $sourceDir , string $targetDir , bool $writeResults RevtagParser::parseInto( $targetDir , $this->targetFiles ); // match and mix + $this->parseTranslationXml(); $this->calculateStatus(); + // fs output if ( $writeResults ) + { QaFileInfo::cacheSave( $this->qaList ); + $this->saveRevcheckData(); + } } private function calculateStatus() { - // All status are marked in source files, - // except notinen, that are marked on target. + // Most of status are marked $sourceFiles, + // except NotInEnTree, that are marked on $targetFiles. + // $revData contains all status foreach( $this->sourceFiles->iterator() as $source ) { @@ -73,6 +80,7 @@ private function calculateStatus() { $source->status = RevcheckStatus::Untranslated; $this->filesUntranslated[] = $source; + $this->addData( $source , null ); continue; } @@ -82,49 +90,58 @@ private function calculateStatus() { $source->status = RevcheckStatus::RevTagProblem; $this->filesRevtagProblem[] = $source; + $this->addData( $source , $target->revtag ); continue; } - // Translation compares ok from multiple hashs. The head hash or the last non-skiped hash. + // Previous code compares uptodate on multiple hashs. The last hash or the last non-skipped hash. // See https://github.com/php/doc-base/blob/090ff07aa03c3e4ad7320a4ace9ffb6d5ede722f/scripts/revcheck.php#L374 // and https://github.com/php/doc-base/blob/090ff07aa03c3e4ad7320a4ace9ffb6d5ede722f/scripts/revcheck.php#L392 . - $sourceHash = $source->head; + $sourceHsh1 = $source->head; + $sourceHsh2 = $source->diff; $targetHash = $target->revtag->revision; - if ( $targetHash == $source->diff ) - $sourceHash = $source->diff; - $daysOld = ( strtotime( "now" ) - $source->date ) / 86400; $daysOld = (int)$daysOld; - $qaInfo = new QaFileInfo( $sourceHash , $targetHash , $this->sourceDir , $this->targetDir , $source->file , $daysOld ); + $qaInfo = new QaFileInfo( $sourceHsh1 , $targetHash , $this->sourceDir , $this->targetDir , $source->file , $daysOld ); $this->qaList[ $source->file ] = $qaInfo; // TranslatedOk - if ( $target->revtag->status == "ready" && $sourceHash == $targetHash ) + if ( $target->revtag->status == "ready" && ( $sourceHsh1 == $targetHash || $sourceHsh2 == $targetHash ) ) { $source->status = RevcheckStatus::TranslatedOk; $this->filesOk[] = $source; + $this->addData( $source , $target->revtag ); continue; } - GitDiffParser::parseNumstatInto( $this->sourceDir , $source ); - + // TranslatedOld // TranslatedWip - if ( $target->revtag->status != "ready" ) + if ( $target->revtag->status == "ready" ) + { + if ( FIXED_SKIP_REVCHECK && $source->diff == "skip" && TestFixedHashMinusTwo( $source->file , $targetHash ) ) + { + $source->status = RevcheckStatus::TranslatedOk; + $this->filesOk[] = $source; + $this->addData( $source , $target->revtag ); + } + else + { + $source->status = RevcheckStatus::TranslatedOld; + $this->filesOld[] = $source; + $this->addData( $source , $target->revtag ); + } + } + else { $source->status = RevcheckStatus::TranslatedWip; $this->filesWip[] = $source; - continue; + $this->addData( $source , $target->revtag ); } - - // TranslatedOld - - $source->status = RevcheckStatus::TranslatedOld; - $this->filesOld[] = $source; } // NotInEnTree @@ -136,7 +153,115 @@ private function calculateStatus() { $target->status = RevcheckStatus::NotInEnTree; $this->filesNotInEn[] = $target; + $this->addData( $target , $target->revtag ); } } + + asort( $this->revData->fileDetail ); } + + private function addData( RevcheckFileInfo $info , RevtagInfo|null $revtag = null ) : void + { + $file = new RevcheckDataFile; + + $file->path = dirname( $info->file ); + $file->name = basename( $info->file ); + $file->size = $info->size; + $file->days = floor( ( time() - $info->date ) / 86400 ); + $file->status = $info->status; + $file->hashLast = $info->head; + $file->hashDiff = $info->diff; + + $this->revData->addFile( $info->file , $file ); + + if ( $revtag != null ) + { + $file->hashRvtg = $revtag->revision; + $file->maintainer = $revtag->maintainer; + $file->completion = $revtag->status; + + $translator = $this->revData->getTranslator( $revtag->maintainer ); + + switch( $info->status ) // counts + { + case RevcheckStatus::TranslatedOk: // ready and synced + $translator->countOk++; + break; + case RevcheckStatus::TranslatedOld: // ready and outdated + $translator->countOld++; + break; + // STATUS_COUNT_MISMATCH count correct + // default: // all other cases + // $translator->countOther++; + + // STATUS_COUNT_MISMATCH backported behaviour + case RevcheckStatus::RevTagProblem: // STATUS_COUNT_MISMATCH backported behaviour + $translator->countOld++; // RevTagProblem into Old (generated diff link fails) + break; + case RevcheckStatus::NotInEnTree: // STATUS_COUNT_MISMATCH backported behaviour + break; // Not counted, but files are listed anyways... + default: + if ( $revtag->status != "ready" ); // STATUS_COUNT_MISMATCH backported behaviour + $translator->countOther++; // The exception of all cases, and also not ready. + break; + // STATUS_COUNT_MISMATCH backported behaviour + } + + switch( $info->status ) // adds,dels + { + case RevcheckStatus::TranslatedOld: + case RevcheckStatus::TranslatedWip: + GitDiffParser::parseAddsDels( $this->sourceDir , $file ); + } + } + } + + private function parseTranslationXml() : void + { + $this->revData = new RevcheckData; + $this->revData->lang = $this->targetDir; + $this->revData->date = date("r"); + + $dom = XmlUtil::loadFile( $this->targetDir . '/translation.xml' ); + + $tag = $dom->getElementsByTagName( 'intro' )[0] ?? null; + if ( $tag == null ) + $intro = "No intro available for the {$this->targetDir} translation of the manual."; + else + { + $intro = ""; + foreach( $tag->childNodes as $node ) + $intro .= $dom->saveXML( $node ); + } + $this->revData->intro = $intro; + + $persons = $dom->getElementsByTagName( 'person' ); + foreach( $persons as $person ) + { + $nick = $person->getAttribute( 'nick' ); + $translator = $this->revData->getTranslator( $nick ); + $translator->name = $person->getAttribute( 'name' ); + $translator->email = $person->getAttribute( 'email' ); + $translator->vcs = $person->getAttribute( 'vcs' ) ?? ""; + } + } + + private function saveRevcheckData() + { + $json = json_encode( $this->revData , JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT ); + file_put_contents( __DIR__ . "/../../../.revcheck.json" , $json ); + } +} + +function TestFixedHashMinusTwo($filename, $hash) :bool +{ + assert( FIXED_SKIP_REVCHECK ); // if deleted, delete entire funciont. + + // See mentions of FIXED_SKIP_REVCHECK on all.php for an explanation + + $cwd = getcwd(); + chdir( 'en' ); + $hashes = explode ( "\n" , `git log -2 --format=%H -- {$filename}` ); + chdir( $cwd ); + return ( $hashes[1] == $hash ); // $trFile->hash } diff --git a/scripts/translation/lib/RevtagParser.php b/scripts/translation/lib/RevtagParser.php index a04db2fd1..cae5eada0 100644 --- a/scripts/translation/lib/RevtagParser.php +++ b/scripts/translation/lib/RevtagParser.php @@ -67,11 +67,11 @@ public static function parseComment( DOMNode $node , RevtagInfo $ret , $filename if ( str_starts_with( $text , "EN-" ) ) { - // /EN-Revision:\s*(\S+)\s*Maintainer:\s*(\S+)\s*Status:\s*(\S+)/ // restrict maintainer without spaces - // /EN-Revision:\s*(\S+)\s*Maintainer:\s(.*?)\sStatus:\s*(\S+)/ // accepts maintainer with spaces + // /EN-Revision:\s*(\S+)\s*Maintainer:\s*(\S+)\s*Status:\s*(\S+)/ // restrict maintainer without spaces + // /EN-Revision:\s*(\S+)\s*Maintainer:\s(.*?)\sStatus:\s*(\S+)/ // accepts maintainer with spaces $match = array(); - $regex = "/EN-Revision:\s*(\S+)\s*Maintainer:\s*(\S+)\s*Status:\s*(\S+)/"; + $regex = "/EN-Revision:\s*(\S+)\s*Maintainer:\s(.*?)\sStatus:\s*(\S+)/"; if ( preg_match( $regex , $text , $match ) ) { $ret->revision = trim( $match[1] ); diff --git a/scripts/translation/lib/all.php b/scripts/translation/lib/all.php index aa9ca28eb..62ec3177d 100644 --- a/scripts/translation/lib/all.php +++ b/scripts/translation/lib/all.php @@ -21,6 +21,7 @@ ini_set( 'display_startup_errors' , 1 ); error_reporting( E_ALL ); +require_once __DIR__ . '/backport.php'; require_once __DIR__ . '/CacheFile.php'; require_once __DIR__ . '/CacheUtil.php'; require_once __DIR__ . '/GitDiffParser.php'; @@ -28,6 +29,7 @@ require_once __DIR__ . '/OutputIgnoreArgv.php'; require_once __DIR__ . '/OutputIgnoreBuffer.php'; require_once __DIR__ . '/QaFileInfo.php'; +require_once __DIR__ . '/RevcheckData.php'; require_once __DIR__ . '/RevcheckFileInfo.php'; require_once __DIR__ . '/RevcheckFileList.php'; require_once __DIR__ . '/RevcheckIgnore.php'; diff --git a/scripts/translation/lib/backport.php b/scripts/translation/lib/backport.php new file mode 100644 index 000000000..455b6e16e --- /dev/null +++ b/scripts/translation/lib/backport.php @@ -0,0 +1,192 @@ +window is the base-2 log of the compression loopback window size. +Higher values (up to 15 -- 32768 bytes) yield better compression at a cost of memory, +while lower values (down to 9 -- 512 bytes) yield worse compression in a smaller memory footprint. +- Default window size is currently 15. ++ Default window size is currently 15. + +memory is a scale indicating how much work memory should be allocated. +Valid values range from 1 (minimal allocation) to 9 (maximum allocation). This memory allocation +diff --git a/install/fpm/configuration.xml b/install/fpm/configuration.xml +index 9baaf43d6f..a34700ef97 100644 +--- a/install/fpm/configuration.xml ++++ b/install/fpm/configuration.xml +@@ -805,109 +805,109 @@ + + + +- %C ++ %C + +%CPU + + +``` + +This commit must be tracked in translations? In other words, this commit +should mark the various files changed as outdated in translations? + +The current implementation on doc-base/revcheck.php would *ignore* this +commit, *not* marking these files as outdated on translations. + +This is because the code searches for '[skip-revcheck]' in any position [1], +and in any lile [2] of commit messages. + +[1] https://github.com/php/doc-base/blob/84532c32eb7b6d694df6cbee3622cec624709654/scripts/revcheck.php#L304 +[2] https://github.com/php/doc-base/blob/84532c32eb7b6d694df6cbee3622cec624709654/scripts/revcheck.php#L302 + +The problem, here, is that this commit on doc-en was squashed, and by so, all +individual commit messages are concatenated in one commit message. + +``` +Remove constant tag from literal values (#3251) +* Remove constant tag from literal values +* [skip-revcheck] Fix whitespace +``` + +The solution proposed is to check for '[skip-revcheck]' mark only at the +starting of the first line on commit messages, so future squashed commits +do not cause file modifications being untracked on translations. + +After code deduplication, open an issue to consider having an +*strick* [skip-revcheck] mode, avoids the issue above, +by removing any mentions of LOOSE_SKIP_REVCHECK constante. */ + +assert( LOOSE_SKIP_REVCHECK || ! LOOSE_SKIP_REVCHECK ); + +/* # FIXED_SKIP_REVCHECK + +Consider the output of: git log --oneline -- reference/ds/ds.deque.xml +``` +4d17 [skip-revcheck] Convert class markup to be compatible with DocBook 5.2 +6cec [skip-revcheck] Normalize &Constants; and &Methods; usage (#2703) +b2a2 These should include the namesapce +120c Document ArrayAccess in PHP-DS +``` + +The last two commits, each one, will mark all their included files as old +in translations, as the commit message does not contain '[skip-revcheck]'. + +The commit 6cec, marked [skip-revcheck], will not mark any file as outdated. + +The commit 4d17, marked [skip-revcheck], will mark all its files as outdated, +needing to be updated to 6cec. + +See the difference in behaviour between two individual commits marked +'[skip-revcheck]'? +That 6cec commit is also marked '[skip-revcheck]' is +incidental. This discrepancy occurs in any sequence of commits +marked '[skip-revcheck]'. + +When the revcheck code, as now, detects that the topmost commit hash contains an +'[skip-revcheck]', it ignores this topmost commit hash, and then selects the fixed +'-2' commit hash as a base of comparison, when the file is calculated as old. + +See: +- Oldness test: https://github.com/php/doc-base/blob/84532c32eb7b6d694df6cbee3622cec624709654/scripts/revcheck.php#L362 +- Topmost skip: https://github.com/php/doc-base/blob/84532c32eb7b6d694df6cbee3622cec624709654/scripts/revcheck.php#L380 +- Hash -2:      https://github.com/php/doc-base/blob/84532c32eb7b6d694df6cbee3622cec624709654/scripts/revcheck.php#L384 + +The output of -2 test, as now, is: +``` +4d17b7b4947e7819ff5036715dd706be87ae4def +6ceccac7860f382f16ac1407baf54f656e85ca0b +``` + +The code linked above splits the results on the new line, and compares the revtag +hash against the second line, 6cec in this case. But 6cec is itself marked as an +'[skip-revcheck]'. So an [skip-revcheck] is bumping all its file hashes into +another [skip-revcheck] commit hash... + +The proposed solution is to removing the use of the fixed -2 topmost hash when +the topmost hash is marked [skip-revcheck] into ignoring any topmost commit +hash marked [skip-revcheck], and thus selecting as an alternative comparison +hash the first topmost hash not marked as [skip-revcheck]. + +In this case, b2a2. + +So any future sequence of [skip-revcheck] commits does not cause the bumping +of hashes in all translations in the presence of a sequence of [skip-revcheck] +commits. + +After code deduplication, open an issue to consider having an +multi skipping [skip-revcheck] mode, avoids the issue above, +by removing any mentions of FIXED_SKIP_REVCHECK constante. */ + +assert( FIXED_SKIP_REVCHECK || ! FIXED_SKIP_REVCHECK ); \ No newline at end of file