From: Sven Eden Date: Mon, 14 May 2018 16:34:03 +0000 (+0200) Subject: migrate_tree.pl: Reworking of the formatted patches added. X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=commitdiff_plain;h=f3ad434bd80ddb838b1013a52c4ed962f09a4d48;p=elogind.git migrate_tree.pl: Reworking of the formatted patches added. --- diff --git a/pwx/migrate_tree.pl b/pwx/migrate_tree.pl index 86c905e36..4760a5178 100755 --- a/pwx/migrate_tree.pl +++ b/pwx/migrate_tree.pl @@ -5,8 +5,9 @@ # ================================================================ # # Version Date Maintainer Changes, Additions, Fixes -# 0.0.1 2017-05-02 sed, PrydeWorX First basic design -# 0.0.2 2017-05-07 sed, PrydeWorX Work flow integrated up to creating the formatted patches +# 0.0.1 2018-05-02 sed, PrydeWorX First basic design. +# 0.0.2 2018-05-07 sed, PrydeWorX Work flow integrated up to creating the formatted patches. +# 0.0.3 2018-05-13 sed, PrydeWorX Reworking of the formatted patches added. # # ======================== # === Little TODO list === @@ -24,13 +25,13 @@ use Try::Tiny; # ================================================================ # === ==> ------ Help Text and Version ----- <== === # ================================================================ -Readonly my $VERSION => "0.0.2"; ## Please keep this current! +Readonly my $VERSION => "0.0.3"; # Please keep this current! Readonly my $VERSMIN => "-" x length($VERSION); Readonly my $PROGDIR => dirname($0); Readonly my $PROGNAME => basename($0); Readonly my $WORKDIR => getcwd(); -Readonly my $CHECK_TREE => abs_path($PROGDIR . "/check_tree.pl"); -Readonly my $COMMIT_FILE => abs_path($PROGDIR . "/last_mutual_commits.csv"); +Readonly my $CHECK_TREE => abs_path( $PROGDIR . "/check_tree.pl" ); +Readonly my $COMMIT_FILE => abs_path( $PROGDIR . "/last_mutual_commits.csv" ); Readonly my $USAGE_SHORT => "$PROGNAME <--help|[OPTIONS] >"; Readonly my $USAGE_LONG => qq# elogind git tree migration V$VERSION @@ -73,76 +74,83 @@ Notes: # === ==> -------- Global variables -------- <== === # ================================================================ -my $commit_count = 0; ## It is easiest to count the relevant commits globally. -my $do_advance = 0; ## If set by --advance, use src- as last commit. -my @lCommits = (); ## List of all relevant commits that have been found, in topological order. -my @lPatches = (); ## List of the formatted patches build from @lCommits. -my $main_result = 1; ## Used for parse_args() only, as simple $result is local everywhere. -my $mutual_commit = ""; ## The last mutual commit to use. Will be read from csv if not set by args. -my $output_path = abs_path("$PROGDIR/patches"); -my $previous_refid = ""; ## Store current upstream state, so we can revert afterwards. -my $show_help = 0; -my @source_files = (); ## Final file list to process, generated in in generate_file_list(). -my $upstream_path = ""; -my $wanted_refid = ""; ## The refid to reset the upstream tree to. - +my $commit_count = 0; # It is easiest to count the relevant commits globally. +my $do_advance = 0; # If set by --advance, use src- as last commit. +my %hDirectories = (); # Filled when searching relevant files, used to validate new files. +my @lCommits = (); # List of all relevant commits that have been found, in topological order. +my @lCreated = (); # List of all files that were created using the migrated commits. +my @lPatches = (); # List of the formatted patches build from @lCommits. +my $main_result = 1; # Used for parse_args() only, as simple $result is local everywhere. +my $mutual_commit = ""; # The last mutual commit to use. Will be read from csv if not set by args. +my $output_path = ""; +my $previous_refid = ""; # Store current upstream state, so we can revert afterwards. +my $prg_line = ""; # Current line when showing progress +my $show_help = 0; +my @source_files = (); # Final file list to process, generated in in generate_file_list(). +my $upstream_path = ""; +my $wanted_refid = ""; # The refid to reset the upstream tree to. # ================================================================ # === ==> ------- MAIN DATA STRUCTURES ------ <== === # ================================================================ -my %hCommits = (); ## Hash of all upstream commits that touch at least one file: - ## ( refid : count of files touched ) -my %hFiles = (); ## List of all source files as %hFile structures with a simple - ## ( tgt : $hFile ) mapping. -my $hFile = {}; ## Simple description of one file consisting of: - ## Note: The store is %hFiles, this is used as a global pointer. - ## Further although the target is the key in %hFiles, we - ## store it here, too, so we always no the $hFile's key. - ## ( patch: Full path to the patch that check_tree.pl would generate - ## src : The potential relative upstream path with 'elogind' substituted by 'systemd' - ## tgt : The found relative path in the local tree - ## ) -my %hMutuals = (); ## Mapping of the $COMMIT_FILE, that works as follows: - ## CSV lines are structured as: - ## src- tgt- - ## They map as follows: - ## ( : { - ## mutual : | This is the last mutual commit - ## src : | This is the last individual commit in the upstream tree (*) - ## tgt : | This is the last individual commit in the local tree (*) - ## } ) - ## (*) When this entry was written. This means that src- can be used as - ## the next last mutual commit, when this migration run is finished. To make - ## this automatic, the --advance option triggers exactly that. +my %hCommits = (); # Hash of all upstream commits that touch at least one file: + # ( refid : count of files touched ) +my %hFiles = (); # List of all source files as %hFile structures with a simple + # ( tgt : $hFile ) mapping. +my $hFile = {}; # Simple description of one file consisting of: + # Note: The store is %hFiles, this is used as a global pointer. + # Further although the target is the key in %hFiles, we + # store it here, too, so we always no the $hFile's key. + # ( patch: Full path to the patch that check_tree.pl would generate + # src : The potential relative upstream path with 'elogind' substituted by 'systemd' + # tgt : The found relative path in the local tree + # ) +my %hMutuals = (); # Mapping of the $COMMIT_FILE, that works as follows: + # CSV lines are structured as: + # src- tgt- + # They map as follows: + # ( : { + # mutual : | This is the last mutual commit + # src : | This is the last individual commit in the upstream tree (*) + # tgt : | This is the last individual commit in the local tree (*) + # } ) + # (*) When this entry was written. This means that src- can be used as + # the next last mutual commit, when this migration run is finished. To make + # this automatic, the --advance option triggers exactly that. + # ================================================================ # === ==> -------- Function list -------- <== === # ================================================================ -sub build_hCommits; ## Build a hash of commits for the current hFile. -sub build_hFile; ## Add an entry to hFiles for a specific target file. -sub build_lCommits; ## Build the topological list of all relevant commits. -sub build_lPatches; ## Fill $output_path with formatted patches from @lCommits. -sub checkout_upstream; ## Checkout the given refid on $upstream_path. -sub generate_file_list; ## Find all relevant files and store them in @wanted_files -sub get_last_mutual; ## Find or read the last mutual refid between this and the upstream tree. -sub parse_args; ## Parse ARGV for the options we support -sub wanted; ## Callback function for File::Find +sub apply_patches; # Apply a reworked patch. +sub build_hCommits; # Build a hash of commits for the current hFile. +sub build_hFile; # Add an entry to hFiles for a specific target file. +sub build_lCommits; # Build the topological list of all relevant commits. +sub build_lPatches; # Fill $output_path with formatted patches from @lCommits. +sub check_tree; # Use check_tree.pl on the given commit and file. +sub checkout_upstream; # Checkout the given refid on $upstream_path. +sub generate_file_list; # Find all relevant files and store them in @wanted_files +sub get_last_mutual; # Find or read the last mutual refid between this and the upstream tree. +sub parse_args; # Parse ARGV for the options we support +sub rework_patch; # Use check_tree.pl to generate valid diffs on all valid files within the patch. +sub show_prg; # Helper to show a progress line that is not permanent. +sub wanted; # Callback function for File::Find # ================================================================ # === ==> -------- Prechecks -------- <== == # ================================================================ --x $CHECK_TREE or die ("$CHECK_TREE not found!"); +-x $CHECK_TREE or die("$CHECK_TREE not found!"); +$output_path = abs_path("$PROGDIR/patches"); $main_result = parse_args(@ARGV); -( (!$main_result) ## Note: Error or --help given, then exit. - or ( $show_help and print "$USAGE_LONG" ) ) - and exit(!$main_result); +( + ( !$main_result ) ## Note: Error or --help given, then exit. + or ( $show_help and print "$USAGE_LONG" ) ) and exit( !$main_result ); get_last_mutual and generate_file_list - or exit 1; -checkout_upstream($wanted_refid) ## Note: Does nothing if $wanted_refid is already checked out. - or exit 1; - + or exit 1; +checkout_upstream($wanted_refid) ## Note: Does nothing if $wanted_refid is already checked out. + or exit 1; # ================================================================ # === ==> -------- = MAIN PROGRAM = -------- <== === @@ -157,7 +165,7 @@ for my $file_part (@source_files) { build_hFile($file_part) or next; build_hCommits or next; } -printf(" %d commits found\n", $commit_count); +printf( " %d commits found\n", $commit_count ); # ----------------------------------------------------------------- # --- 2) Get a list of all commits and build @lCommits, checking -- @@ -169,10 +177,42 @@ build_lCommits or exit 1; # ----------------------------------------------------------------- # --- 3) Go through the relevant commits and create formatted --- -# --- patches for them using. --- +# --- patches for them. --- # ----------------------------------------------------------------- build_lPatches or exit 1; +# ----------------------------------------------------------------- +# --- 4) Go through the patches and rewrite them. We only want --- +# --- them to touch files of relevance, and need them to --- +# --- contain only diffs that are valid for us. We'll use --- +# --- check_tree.pl to achieve the latter. --- +# ----------------------------------------------------------------- +for ( my $i = 0 ; $i < $commit_count ; ++$i ) { + my $fmt = sprintf( "%04d-*.patch", $i + 1 ); + my @lFiles = glob qq("${output_path}/${fmt}"); + + # Be sure this is solid! + # ---------------------------------------------------------- + if ( scalar @lFiles > 1 ) { + print "\nERROR: $fmt results in more than one patch!\n"; + return 0; + } elsif ( 1 > scalar @lFiles ) { + print "\nERROR: No patches found for $fmt!"; + return 0; + } + + show_prg( sprintf("Reworking %s"), basename( $lFiles[0] ) ); + rework_patch( $lFiles[0] ) or exit 1; + + # ------------------------------------------------------------- + # --- 5) Reworked patches must be applied directly. --- + # --- Otherwise we'll screw up if a newly created file --- + # --- gets patched later. --- + # ------------------------------------------------------------- + show_prg( sprintf("Applying %s"), basename( $lFiles[0] ) ); + apply_patch( $lFiles[0] ) or exit 1; +} ## end for ( my $i = 0 ; $i < ...) +show_prg(""); # =========================== # === END OF MAIN PROGRAM === @@ -188,33 +228,32 @@ length($previous_refid) and checkout_upstream($previous_refid); # === ==> ---- Function Implementations ---- <== === # ================================================================ +# -------------------------------------------------------------- +# --- Apply a reworked patch --- +# -------------------------------------------------------------- +sub apply_patch { + my ($pFile) = @_; + + return 1; +} # ------------------------------------------------------ # --- Build a hash of commits for the current hFile. --- # ------------------------------------------------------ sub build_hCommits { my $git = Git::Wrapper->new($upstream_path); - - my @lRev = $git->rev_list( { - topo_order => 1, - "reverse" => 1, - oneline => 1 - }, - "${mutual_commit}..${wanted_refid}", - $hFile->{src} ); + my @lRev = $git->rev_list( { topo_order => 1, "reverse" => 1, oneline => 1 }, "${mutual_commit}..${wanted_refid}", $hFile->{src} ); for my $line (@lRev) { if ( $line =~ m/^(\S+)\s+/ ) { - defined($hCommits{$1}) - or ++$commit_count - and $hCommits{$1} = 0; + defined( $hCommits{$1} ) + or ++$commit_count and $hCommits{$1} = 0; ++$hCommits{$1}; } - } + } ## end for my $line (@lRev) return 1; -} - +} ## end sub build_hCommits # ------------------------------------------------------------------ # --- Build a list of the relevant commits in topological order. --- @@ -222,23 +261,19 @@ sub build_hCommits { sub build_lCommits { my $git = Git::Wrapper->new($upstream_path); - my @lRev = $git->rev_list( { - topo_order => 1, - "reverse" => 1, - oneline => 1 - }, - "${mutual_commit}..${wanted_refid}" ); + my @lRev = $git->rev_list( { topo_order => 1, "reverse" => 1, oneline => 1 }, "${mutual_commit}..${wanted_refid}" ); for my $line (@lRev) { if ( $line =~ m/^(\S+)\s+/ ) { - defined($hCommits{$1}) - and push @lCommits, "$1"; + defined( $hCommits{$1} ) + and show_prg("Noting down $1") + and push @lCommits, "$1"; } - } - - return 1; -} + } ## end for my $line (@lRev) + show_prg(""); + return 1; +} ## end sub build_lCommits # ---------------------------------------------------------- # --- Add an entry to hFiles for a specific target file. --- @@ -262,71 +297,129 @@ sub build_hFile { $patch =~ s/\//_/g; # Build the central data structure. - %hFiles = ( - $tgt => { - patch => $output_path . "/" . $patch . ".patch", - src => $src, - tgt => $tgt - } ); - + $hFiles{$tgt} = { + patch => $output_path . "/" . $patch . ".patch", + src => $src, + tgt => $tgt + }; + # This is now our current hFile $hFile = $hFiles{$tgt}; return 1; -} - +} ## end sub build_hFile # ---------------------------------------------------------------- # --- Fill $output_path with formatted patches from @lCommits. --- # ---------------------------------------------------------------- sub build_lPatches { - my $git = Git::Wrapper->new($upstream_path); - my $cnt = 0; - my $curLen = 0; - my $maxLen = 0; - my @lRev = (); - my @lPath = (); + my $git = Git::Wrapper->new($upstream_path); + my $cnt = 0; + my @lRev = (); + my @lPath = (); for my $refid (@lCommits) { - @lRev = $git->rev_list( { - "1" => 1, - oneline => 1 - }, $refid ); + @lRev = $git->rev_list( { "1" => 1, oneline => 1 }, $refid ); - $curLen = length($lRev[0]); - $curLen > $maxLen and $maxLen = $curLen; - printf("\r%03d: %s", ++$cnt, $lRev[0] - . ($maxLen > $curLen ? ' ' x ($maxLen - $curLen) : "")); + show_prg( sprintf( "Building %03d: %s", ++$cnt, $lRev[0] ) ); try { - @lPath = $git->format_patch({ - "1" => 1, - "find-copies" => 1, - "find-copies-harder" => 1, - "numbered" => 1, - "output-directory" => $output_path - }, - "--start-number=$cnt", - $refid); - } catch { + @lPath = $git->format_patch( + { + "1" => 1, + "find-copies" => 1, + "find-copies-harder" => 1, + "numbered" => 1, + "output-directory" => $output_path + }, + "--start-number=$cnt", + $refid + ); + } ## end try + catch { print "\nERROR: Couldn't format-patch $refid\n"; print "Exit Code : " . $_->status . "\n"; - print "Message : " . $_->error . "\n"; + print "Message : " . $_->error . "\n"; return 0; }; + } ## end for my $refid (@lCommits) + $cnt and show_prg("") and print("$cnt patches built\n"); + + # Just a safe guard, that is almost guaranteed to never catch. + if ( $cnt != $commit_count ) { + print "ERROR: $commit_count patches expected, but only $cnt patches generated!\n"; + return 0; } - $maxLen and print "\r" . (' ' x $maxLen) . "\r$cnt patches built\n"; - + return 1; -} +} ## end sub build_lPatches + +# ------------------------------------------------------- +# --- Use check_tree.pl on the given commit and file. --- +# ------------------------------------------------------- +sub check_tree { + my ( $commit, $file, $isNew ) = @_; + my $stNew = ""; + + # If this is the creation of a new file, the hFile must be built. + if ($isNew) { + my $tgt_file = basename($file); + my $tgt_dir = dirname($file); + $tgt_file =~ s/systemd/elogind/g; + + defined( $hDirectories{$tgt_dir} ) + or $tgt_dir =~ s/systemd/elogind/g; + + my $tgt = "$tgt_dir/$tgt_file"; + + # Build the patch name + my $patch = $tgt; + $patch =~ s/\//_/g; + $hFiles{$file} = { + patch => $output_path . "/" . $patch . ".patch", + src => $file, + tgt => $tgt + }; + $stNew = "--create "; + } ## end if ($isNew) + my $path = $hFiles{$file}{patch}; + + # Now get the patch built + my @lResult = `$CHECK_TREE --stay -c $commit ${stNew}-f $file $upstream_path 2>&1`; + my $res = $?; + my $err = $!; + + if ($res) { + print "\n$CHECK_TREE died!\n"; + + if ( $res == -1 ) { + print "failed to execute: $err\n"; + } elsif ( $? & 127 ) { + printf "Signal %d, %s coredump\n", ( $? & 127 ), ( $? & 128 ) ? 'with' : 'without'; + } else { + printf "child exited with value %d\n", $? >> 8; + } + print "-----\n" . join( "", @lResult ) . "-----\n"; + return ""; + } ## end if ($res) + + # If check_tree found no diff or cleaned up all hunks, no patch is created. + for my $line (@lResult) { + chomp $line; + if ( $line =~ m/${file}:\s+(clean|same)/ ) { + return "none"; + } + } ## end for my $line (@lResult) + return $path; +} ## end sub check_tree # ----------------------------------------------------------------------- # --- Checkout the given refid on $upstream_path --- # --- Returns 1 on success, 0 otherwise. --- # ----------------------------------------------------------------------- sub checkout_upstream { - my ($commit) = @_; + my ($commit) = @_; # It is completely in order to not wanting to checkout a specific commit. defined($commit) and length($commit) or return 1; @@ -337,43 +430,45 @@ sub checkout_upstream { # Save the previous commit try { - @lOut = $git->rev_parse({short => 1}, "HEAD"); - } catch { + @lOut = $git->rev_parse( { short => 1 }, "HEAD" ); + } + catch { print "ERROR: Couldn't rev-parse $upstream_path HEAD\n"; print "Exit Code : " . $_->status . "\n"; - print "Message : " . $_->error . "\n"; + print "Message : " . $_->error . "\n"; return 0; }; $previous_refid = $lOut[0]; # Get the shortened commit hash of $commit try { - @lOut = $git->rev_parse({short => 1}, $commit); - } catch { + @lOut = $git->rev_parse( { short => 1 }, $commit ); + } + catch { print "ERROR: Couldn't rev-parse $upstream_path \"$commit\"\n"; print "Exit Code : " . $_->status . "\n"; - print "Message : " . $_->error . "\n"; + print "Message : " . $_->error . "\n"; return 0; }; $new_commit = $lOut[0]; # Now check it out, unless we are already there: - if ($previous_refid ne $new_commit) { + if ( $previous_refid ne $new_commit ) { print "Checking out $new_commit in upstream tree..."; try { $git->checkout($new_commit); - } catch { + } + catch { print "\nERROR: Couldn't checkout \"new_commit\" in $upstream_path\n"; print "Exit Code : " . $_->status . "\n"; - print "Message : " . $_->error . "\n"; + print "Message : " . $_->error . "\n"; return 0; }; print " done\n"; - } + } ## end if ( $previous_refid ne...) return 1; -} - +} ## end sub checkout_upstream # ----------------------------------------------------------------------- # --- Finds all relevant files and store them in @wanted_files --- @@ -390,30 +485,36 @@ sub generate_file_list { # The idea now is, that we use File::Find to search for files # in all legal directories this program allows. print "Find relevant files..."; - for my $xDir ("docs", "factory", "m4", "man", "shell-completion", "src", "tools") { + for my $xDir ( "docs", "factory", "m4", "man", "shell-completion", "src", "tools" ) { if ( -d "$xDir" ) { - find(\&wanted, "$xDir"); + find( \&wanted, "$xDir" ); } } # There are also root files we need to check. Thanks to the usage of # check_tree.pl to generate the later commit diffs, these can be safely # added to our file list as well. - for my $xFile ("Makefile", "Makefile.am", "TODO", "CODING_STYLE", "configure", - ".mailmap", "LICENSE.GPL2", "meson_options.txt", "NEWS", - "meson.build", "configure.ac", ".gitignore") { + for my $xFile ( "Makefile", "Makefile.am", "TODO", "CODING_STYLE", "configure", ".mailmap", "LICENSE.GPL2", "meson_options.txt", "NEWS", "meson.build", "configure.ac", ".gitignore" ) { -f "$xFile" and push @source_files, "./$xFile"; } - print " done\n"; + print " done - " . ( scalar @source_files ) . " files found\n"; # Just to be sure... scalar @source_files - or print("ERROR: No source files found? Where the hell are we?\n") - and return 0; + or print("ERROR: No source files found? Where the hell are we?\n") + and return 0; + + # Eventually we can add each directory to %hDirectories + for my $xFile (@source_files) { + my $xDir = dirname($xFile); + $xDir =~ s,^\./,,; + if ( length($xDir) > 1 ) { + defined( $hDirectories{$xDir} ) or $hDirectories{$xDir} = 1; + } + } ## end for my $xFile (@source_files) return 1; -} - +} ## end sub generate_file_list # ------------------------------------------------------------------------------ # --- Find or read the last mutual refid between this and the upstream tree. --- @@ -423,54 +524,47 @@ sub get_last_mutual { # No matter whether the commit is set or not, we need to read the # commit file now, and write it back later if we have changes. if ( -f $COMMIT_FILE ) { - if (open my $fIn, "<", $COMMIT_FILE) { + if ( open my $fIn, "<", $COMMIT_FILE ) { my @lLines = <$fIn>; close $fIn; - + for my $line (@lLines) { chomp $line; if ( $line =~ m/^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s*$/ ) { my $ref = $1; my $src = $3; my $tgt = $4; - $hMutuals{$ref} = { - mutual => $2, - src => undef, - tgt => undef - }; + $hMutuals{$ref} = { mutual => $2, src => undef, tgt => undef }; $src =~ m/^src-(\S+)$/ and $hMutuals{$ref}{src} = $1; $tgt =~ m/^tgt-(\S+)$/ and $hMutuals{$ref}{tgt} = $1; - } - } + } ## end if ( $line =~ m/^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s*$/) + } ## end for my $line (@lLines) } else { print("ERROR: $COMMIT_FILE can not be read!\n$!\n"); return 0; } - } + } ## end if ( -f $COMMIT_FILE ) # If this is already set, we are fine. if ( length($mutual_commit) ) { $hMutuals{$wanted_refid}{mutual} = $mutual_commit; return 1; } - + # Now check against --advance and then set $mutual_commit accordingly. - if (defined($hMutuals{$wanted_refid})) { + if ( defined( $hMutuals{$wanted_refid} ) ) { if ($do_advance) { - defined($hMutuals{$wanted_refid}{src}) - and $hMutuals{$wanted_refid}{mutual} = $hMutuals{$wanted_refid}{src} - or print "ERROR: --advance set, but no source hash found!\n" - and return 0; + defined( $hMutuals{$wanted_refid}{src} ) and $hMutuals{$wanted_refid}{mutual} = $hMutuals{$wanted_refid}{src} + or print "ERROR: --advance set, but no source hash found!\n" and return 0; } $mutual_commit = $hMutuals{$wanted_refid}{mutual}; return 1; - } - + } ## end if ( defined( $hMutuals...)) + print "ERROR: There is no last mutual commit known for refid \"$wanted_refid\"!\n"; - - return 0; -} + return 0; +} ## end sub get_last_mutual # ----------------------------------------------------------------------- # --- parse the given list for arguments. --- @@ -478,49 +572,51 @@ sub get_last_mutual { # --- sets global $show_help to 1 if the long help should be printed. --- # ----------------------------------------------------------------------- sub parse_args { - my @args = @_; - my $result = 1; + my @args = @_; + my $result = 1; - for (my $i = 0; $i < @args; ++$i) { + for ( my $i = 0 ; $i < @args ; ++$i ) { # Check --advance option - if ($args[$i] =~ m/^--advance$/) { + if ( $args[$i] =~ m/^--advance$/ ) { $do_advance = 1; } # Check for -c|--commit option # ------------------------------------------------------------------------------- - elsif ($args[$i] =~ m/^-(?:c|-commit)$/) { - if ( ( ($i + 1) >= @args ) - || ( $args[$i+1] =~ m,^[-/.], ) ) { + elsif ( $args[$i] =~ m/^-(?:c|-commit)$/ ) { + if ( ( ( $i + 1 ) >= @args ) + || ( $args[ $i + 1 ] =~ m,^[-/.], ) ) + { print "ERROR: Option $args[$i] needs a refid as argument!\n\nUsage: $USAGE_SHORT\n"; $result = 0; next; - } - $mutual_commit = $args[++$i]; - } + } ## end if ( ( ( $i + 1 ) >= @args...)) + $mutual_commit = $args[ ++$i ]; + } ## end elsif ( $args[$i] =~ m/^-(?:c|-commit)$/) # Check for -h|--help option # ------------------------------------------------------------------------------- - elsif ($args[$i] =~ m/^-(?:h|-help)$/) { + elsif ( $args[$i] =~ m/^-(?:h|-help)$/ ) { $show_help = 1; } # Check for -o|--output option # ------------------------------------------------------------------------------- - elsif ($args[$i] =~ m/^-(?:o|-output)$/) { - if ( ( ($i + 1) >= @args ) - || ( $args[$i+1] =~ m,^[-/.], ) ) { + elsif ( $args[$i] =~ m/^-(?:o|-output)$/ ) { + if ( ( ( $i + 1 ) >= @args ) + || ( $args[ $i + 1 ] =~ m,^[-/.], ) ) + { print "ERROR: Option $args[$i] needs a path as argument!\n\nUsage: $USAGE_SHORT\n"; $result = 0; next; - } - $output_path = abs_path($args[++$i]); - } + } ## end if ( ( ( $i + 1 ) >= @args...)) + $output_path = abs_path( $args[ ++$i ] ); + } ## end elsif ( $args[$i] =~ m/^-(?:o|-output)$/) # Check for unknown options: # ------------------------------------------------------------------------------- - elsif ($args[$i] =~ m/^-/) { + elsif ( $args[$i] =~ m/^-/ ) { print "ERROR: Unknown option \"$args[$i]\" encountered!\n\nUsage: $USAGE_SHORT\n"; $result = 0; } @@ -529,51 +625,212 @@ sub parse_args { # ------------------------------------------------------------------------------- else { # But only if they are not set, yet: - if (length($upstream_path) && length($wanted_refid)) { + if ( length($upstream_path) && length($wanted_refid) ) { print "ERROR: Superfluous argument \"$args[$i]\" found!\n\nUsage: $USAGE_SHORT\n"; $result = 0; next; } - if (length($upstream_path) ) { + if ( length($upstream_path) ) { $wanted_refid = "$args[$i]"; } else { - if ( ! -d "$args[$i]") { + if ( !-d "$args[$i]" ) { print "ERROR: Upstream path \"$args[$i]\" does not exist!\n\nUsage: $USAGE_SHORT\n"; $result = 0; next; } - $upstream_path = abs_path($args[$i]); - } - } - } ## End looping arguments + $upstream_path = abs_path( $args[$i] ); + } ## end else [ if ( length($upstream_path...))] + } ## end else [ if ( $args[$i] =~ m/^--advance$/)] + } ## End looping arguments # If we have no refid now, show short help. - if ($result && !$show_help && !length($wanted_refid) ) { + if ( $result && !$show_help && !length($wanted_refid) ) { print "ERROR: Please provide a path to upstream and a refid!\n\nUsage: $USAGE_SHORT\n"; $result = 0; } # If both --advance and --commit were used, we can not tell what the # user really wants. So better be safe here, or we might screw the tree! - if ($do_advance && length($mutual_commit)) { + if ( $do_advance && length($mutual_commit) ) { print "ERROR: You have used both --advance and --commit.\n"; print " Which one is the one you really want?\n\n"; print "Usage: $USAGE_SHORT\n"; $result = 0; - } + } ## end if ( $do_advance && length...) return $result; -} ## parse_srgs() end +} ## parse_srgs() end + +# -------------------------------------------------------------- +# --- Use check_tree.pl to generate valid diffs on all valid --- +# --- files within the patch with the given number. --- +# -------------------------------------------------------------- +sub rework_patch { + my ($pFile) = @_; + my @lLines = (); + + if ( open( my $fIn, "<", $pFile ) ) { + @lLines = <$fIn>; + close($fIn); + chomp(@lLines); + } else { + print "\nERROR: $pFile could not be opened for reading!\n$!\n"; + return 0; + } + + # Copy the header, ended by either '---' or 'diff ' + # ---------------------------------------------------------- + my @lOut = (); + my $lCnt = scalar @lLines; + my $commit = ""; + + while ( $lCnt-- > 0 ) { + + # Can not be done in while(), or empty lines would break the loop. + my $line = shift @lLines; + + # We break this once we have found a file summary line + if ( $line =~ m/^\s+(\S+)\s+\|\s+\d+/ ) { + unshift @lLines, $line; ## Still needed + ++$lCnt; ## Yeah, put it up again! + last; + } + + # Before transfering the line, see if this is the commit info + $line =~ m/^From (\S+)\s\w{3}\s\w{3}\s\d{2}/ and $commit = $1; + + push @lOut, $line; + } ## end while ( $lCnt-- > 0 ) + # There is something wrong if we have no commit hash now + if ( 0 == length($commit) ) { + print "\nERROR: No 'From ' line found!\n"; + return 0; + } + + # There is something wrong if the next part is not a file summary line. + # ---------------------------------------------------------------------- + if ( !defined( $lLines[0] ) || !( $lLines[0] =~ m/^\s+(\S+)\s+\|\s+\d+/ ) ) { + print "\nERROR: No file summary block found!\n"; + print "The line currently looked at is:\n"; + print "|" . ( defined( $lLines[0] ) ? $lLines[0] : "UNDEF" ) . "|\n"; + print "We still have $lCnt lines to go.\n"; + print "\nlOut so far:\n" . join( "\n", @lOut ) . "\n---------- END\n"; + return 0; + } ## end if ( !defined( $lLines...)) + + my @lFixedPatches = (); + while ( $lCnt-- > 0 ) { + my $isNew = 0; ## 1 if we hit a creation summary line + my $line = shift @lLines; + my $real = ""; ## The real file to work with + my $src = ""; ## Source in upstream + my $tgt = ""; ## Target in downstream + + # This ends on the first empty line. + $line =~ m/^\s*$/ and push( @lOut, "" ) and last; + + # This is either a line modification information, or a + # creation line of a new file. These look like this... + # src/shared/meson.build | 1 + + $line =~ m/^\s+(\S+)\s+\|.*/ and $src = $1; + + # ...or that: + # create mode 100644 src/network/netdev/wireguard.c + $line =~ m/^\s+create\s+mode\s+\d+\s+(\S+)\s*$/ + and $src = $1 + and $isNew = 1; + + # Otherwise it is the modification summary line + length($src) or push( @lOut, $line ) and next; + + $tgt = $src; + $tgt =~ s/systemd/elogind/g; + + # The determination what is valid is different for whether this is + # the modification of an existing or the creation of a new file + if ($isNew) { + defined( $hDirectories{ dirname($src) } ) and $real = $src; + defined( $hDirectories{ dirname($tgt) } ) and $real = $tgt; + } else { + + # Try the renamed first, then the non-renamed + defined( $hFiles{$tgt} ) and $real = $tgt + or defined( $hFiles{$src} ) + and $real = $src; + } ## end else [ if ($isNew) ] + + # We neither need diffs on invalid files, nor new files in invalid directories. + length($real) or next; + + # Now use $real to get the patch needed, if it is set. + my $pNew = check_tree( $commit, $real, $isNew ); + + # If no patch was generated, the file is either "same" or "clean". + $pNew eq "none" and next; + + # However, an empty $pNew indicates an error. (check_tree() has it reported already.) + length($pNew) and push @lFixedPatches, $pNew or return 0; + + # If we are here, transfer the file line. It is useful. + push @lOut, $line; + } ## End of scanning lines + + scalar @lFixedPatches or next; + + # Load all generated patches and add them to lOut + # ---------------------------------------------------------- + for my $pNew (@lFixedPatches) { + if ( open my $fIn, "<", $pNew ) { + my @lNew = <$fIn>; + close($fIn); + chomp(@lNew); + push @lOut, @lNew; + unlink $pNew; + } else { + print "\nERROR: Can't open $pNew for reading!\n$!\n"; + return 0; + } + } ## end for my $pNew (@lFixedPatches) + + # Eventually overwrite $pFile with @lOut + # ---------------------------------------------------------- + if ( open( my $fOut, ">", $pFile ) ) { + print $fOut join( "\n", @lOut ) . "\n"; + close($fOut); + } else { + print "\nERROR: Can not opne $pFile for writing!\n$!\n"; + } + + return 1; +} ## end sub rework_patch + +# Helper to show the argument as a non permanent progress line. +sub show_prg { + my ($msg) = @_; + my $len = length($prg_line); + + $len and print "\r" . ( ' ' x $len ) . "\r"; + + $prg_line = $msg; + + if ( length($prg_line) ) { + local $| = 1; + print $msg; + } + + return 1; +} ## end sub show_prg # Callback function for File::Find sub wanted { my $f = $File::Find::name; - $f =~ m,^\./, or $f = "./$f"; + $f =~ m,^\./, or $f = "./$f"; - -f $_ and (! ($_ =~ m/\.pwx$/ ) ) - and push @source_files, $File::Find::name; + -f $_ + and ( !( $_ =~ m/\.pwx$/ ) ) + and push @source_files, $File::Find::name; return 1; -} +} ## end sub wanted