3 # ================================================================
4 # === ==> -------- HISTORY -------- <== ===
5 # ================================================================
7 # Version Date Maintainer Changes, Additions, Fixes
8 # 0.0.1 2018-05-02 sed, PrydeWorX First basic design.
9 # 0.0.2 2018-05-07 sed, PrydeWorX Work flow integrated up to creating the formatted patches.
10 # 0.0.3 2018-05-13 sed, PrydeWorX Reworking of the formatted patches added.
11 # 0.1.0 2018-05-14 sed, PrydeWorX Application of the reworked patches added.
12 # 0.2.0 2018-05-15 sed, PrydeWorX First working version.
13 # 0.2.1 Fixed usage of Try::Tiny.
14 # 0.2.2 2018-05-16 sed, PrydeWorX Made sure that the commit file is always written on exit,
15 # but only if a potential commits file was finished reading.
17 # ========================
18 # === Little TODO list ===
19 # ========================
23 use Cwd qw(getcwd abs_path);
30 # ================================================================
31 # === ==> ------ Help Text and Version ----- <== ===
32 # ================================================================
33 Readonly my $VERSION => "0.2.2"; # Please keep this current!
34 Readonly my $VERSMIN => "-" x length($VERSION);
35 Readonly my $PROGDIR => dirname($0);
36 Readonly my $PROGNAME => basename($0);
37 Readonly my $WORKDIR => getcwd();
38 Readonly my $CHECK_TREE => abs_path( $PROGDIR . "/check_tree.pl" );
39 Readonly my $COMMIT_FILE => abs_path( $PROGDIR . "/last_mutual_commits.csv" );
40 Readonly my $USAGE_SHORT => "$PROGNAME <--help|[OPTIONS] <upstream path> <refid>>";
41 Readonly my $USAGE_LONG => qq#
42 elogind git tree migration V$VERSION
43 ----------------------------$VERSMIN
45 Reset the git tree in <upstream path> to the <refid>. The latter can be any
46 commit, branch or tag. Then search its history since the last mutual commit
47 for any commit that touches at least one file in any subdirectory of the
48 directory this script was called from.
50 Please note that this program was written especially for elogind. It is very
51 unlikely that it can be used in any other project.
57 --advance : Use the last upstream commit that has been written
58 into "$COMMIT_FILE" as the last
59 mutual commit to use. This is useful for continued
60 migration of branches. Incompatible with -c|--commit.
61 -c|--commit <hash> : The mutual commit to use. If this option is not used,
62 the script looks into "$COMMIT_FILE"
63 and uses the commit noted for <refid>. Incompatible
65 -h|--help Show this help and exit.
66 -o|--output <path> : Path to where to write the patches. The default is to
67 write into "$PROGDIR/patches".
70 - The upstream tree is reset and put back into the current state after the
72 - When the script succeeds, it adds a line to "$COMMIT_FILE"
74 <tag>-last <newest found commit>. You can use that line for the next
75 <refid> you wish to migrate to.
78 # ================================================================
79 # === ==> -------- Global variables -------- <== ===
80 # ================================================================
82 my $commit_count = 0; # It is easiest to count the relevant commits globally.
83 my $commits_read = 0; # Set to one once the commit file is completely read.
84 my $do_advance = 0; # If set by --advance, use src-<hash> as last commit.
85 my %hSrcCommits = (); # Record here which patch file is which commit.
86 my %hDirectories = (); # Filled when searching relevant files, used to validate new files.
87 my @lCommits = (); # List of all relevant commits that have been found, in topological order.
88 my @lCreated = (); # List of all files that were created using the migrated commits.
89 my @lPatches = (); # List of the formatted patches build from @lCommits.
90 my $main_result = 1; # Used for parse_args() only, as simple $result is local everywhere.
91 my $mutual_commit = ""; # The last mutual commit to use. Will be read from csv if not set by args.
93 my $previous_refid = ""; # Store current upstream state, so we can revert afterwards.
94 my $prg_line = ""; # Current line when showing progress
96 my @source_files = (); # Final file list to process, generated in in generate_file_list().
97 my $upstream_path = "";
98 my $wanted_refid = ""; # The refid to reset the upstream tree to.
100 # ================================================================
101 # === ==> ------- MAIN DATA STRUCTURES ------ <== ===
102 # ================================================================
103 my %hCommits = (); # Hash of all upstream commits that touch at least one file:
104 # ( refid : count of files touched )
105 my %hFiles = (); # List of all source files as %hFile structures with a simple
106 # ( tgt : $hFile ) mapping.
107 my $hFile = {}; # Simple description of one file consisting of:
108 # Note: The store is %hFiles, this is used as a global pointer.
109 # Further although the target is the key in %hFiles, we
110 # store it here, too, so we always no the $hFile's key.
111 # ( patch: Full path to the patch that check_tree.pl would generate
112 # src : The potential relative upstream path with 'elogind' substituted by 'systemd'
113 # tgt : The found relative path in the local tree
115 my %hMutuals = (); # Mapping of the $COMMIT_FILE, that works as follows:
116 # CSV lines are structured as:
117 # <refid> <hash> src-<hash> tgt-<hash>
118 # They map as follows:
119 # ( <path to upstream tree> {
121 # mutual : <hash> | This is the last mutual commit
122 # src : <hash> | This is the last individual commit in the upstream tree (*)
123 # tgt : <hash> | This is the last individual commit in the local tree (*)
125 # (*) When this entry was written. This means that src-<hash> can be used as
126 # the next last mutual commit, when this migration run is finished. To make
127 # this automatic, the --advance option triggers exactly that.
129 # ================================================================
130 # === ==> -------- Function list -------- <== ===
131 # ================================================================
133 sub apply_patches; # Apply a reworked patch.
134 sub build_hCommits; # Build a hash of commits for the current hFile.
135 sub build_hFile; # Add an entry to hFiles for a specific target file.
136 sub build_lCommits; # Build the topological list of all relevant commits.
137 sub build_lPatches; # Fill $output_path with formatted patches from @lCommits.
138 sub check_tree; # Use check_tree.pl on the given commit and file.
139 sub checkout_tree; # Checkout the given refid on the given path.
140 sub generate_file_list; # Find all relevant files and store them in @wanted_files
141 sub get_last_mutual; # Find or read the last mutual refid between this and the upstream tree.
142 sub handle_sig; # Signal handler so we don't break without writing a new commit file.
143 sub parse_args; # Parse ARGV for the options we support
144 sub rework_patch; # Use check_tree.pl to generate valid diffs on all valid files within the patch.
145 sub set_last_mutual; # Write back %hMutuals to $COMMIT_FILE
146 sub shorten_refid; # Take DIR and REFID and return the shortest possible REFID in DIR.
147 sub show_prg; # Helper to show a progress line that is not permanent.
148 sub wanted; # Callback function for File::Find
150 # set signal-handlers
151 local $SIG{'INT'} = \&handle_sig;
152 local $SIG{'QUIT'} = \&handle_sig;
153 local $SIG{'TERM'} = \&handle_sig;
155 # ================================================================
156 # === ==> -------- Prechecks -------- <== ==
157 # ================================================================
159 -x $CHECK_TREE or die("$CHECK_TREE not found!");
161 $output_path = abs_path("$PROGDIR/patches");
162 $main_result = parse_args(@ARGV);
164 ( !$main_result ) ## Note: Error or --help given, then exit.
165 or ( $show_help and print "$USAGE_LONG" ) ) and exit( !$main_result );
166 get_last_mutual and generate_file_list
168 checkout_tree($upstream_path, $wanted_refid, 1)
171 # ================================================================
172 # === ==> -------- = MAIN PROGRAM = -------- <== ===
173 # ================================================================
175 # -----------------------------------------------------------------
176 # --- 1) Go through all files and generate a list of all source ---
177 # --- commits that touch the file. ---
178 # -----------------------------------------------------------------
179 print "Searching relevant commits ...";
180 for my $file_part (@source_files) {
181 build_hFile($file_part) or next;
182 build_hCommits or next;
184 printf( " %d commits found\n", $commit_count );
186 # -----------------------------------------------------------------
187 # --- 2) Get a list of all commits and build @lCommits, checking --
188 # --- against the found hashes in $hCommits. This will build ---
189 # --- a list that has the correct order the commits must be ---
191 # -----------------------------------------------------------------
192 build_lCommits or exit 1;
194 # -----------------------------------------------------------------
195 # --- 3) Go through the relevant commits and create formatted ---
196 # --- patches for them. ---
197 # -----------------------------------------------------------------
198 build_lPatches or exit 1;
200 # -----------------------------------------------------------------
201 # --- 4) Go through the patches and rewrite them. We only want ---
202 # --- them to touch files of relevance, and need them to ---
203 # --- contain only diffs that are valid for us. We'll use ---
204 # --- check_tree.pl to achieve the latter. ---
205 # -----------------------------------------------------------------
206 for ( my $i = 0 ; $i < $commit_count ; ++$i ) {
207 my $fmt = sprintf( "%04d-*.patch", $i + 1 );
208 my @lFiles = glob qq("${output_path}/${fmt}");
210 # Be sure this is solid!
211 # ----------------------------------------------------------
212 if ( scalar @lFiles > 1 ) {
213 print "\nERROR: $fmt results in more than one patch!\n";
215 } elsif ( 1 > scalar @lFiles ) {
216 print "\nERROR: No patches found for $fmt!";
220 show_prg( sprintf("Reworking %s", basename( $lFiles[0] ) ) );
221 rework_patch( $lFiles[0] ) or exit 1;
223 # If the patch was eventually empty, rework_patch() has deleted it.
224 -f $lFiles[0] or next;
226 # -------------------------------------------------------------
227 # --- 5) Reworked patches must be applied directly. ---
228 # --- Otherwise we'll screw up if a newly created file ---
229 # --- gets patched later. ---
230 # -------------------------------------------------------------
231 show_prg( sprintf("Applying %s", basename( $lFiles[0] ) ) );
232 apply_patch( $lFiles[0] ) or exit 1;
234 # The patch file is no longer needed. Keeping it would lead to confusion.
236 } ## end for ( my $i = 0 ; $i < ...)
240 # ===========================
241 # === END OF MAIN PROGRAM ===
242 # ===========================
244 # ================================================================
245 # === ==> -------- Cleanup -------- <== ===
246 # ================================================================
250 length($previous_refid) and checkout_tree($upstream_path, $previous_refid, 0);
254 # ================================================================
255 # === ==> ---- Function Implementations ---- <== ===
256 # ================================================================
258 # --------------------------------------------------------------
259 # --- Apply a reworked patch ---
260 # --------------------------------------------------------------
263 my $git = Git::Wrapper->new($WORKDIR);
265 my $patch_lines = "";
267 # --- 1) Read the patch, we have to use it directly via STDIN ---
268 # ---------------------------------------------------------------
269 if ( open( my $fIn, "<", $pFile ) ) {
273 $patch_lines = join( "\n", @lLines ) . "\n";
275 print "\nERROR: $pFile could not be opened for reading!\n$!\n";
279 # --- 2) Try to apply the patch as is ---
280 # ---------------------------------------------------------------
283 @lGitRes = $git->am( {
285 -STDIN => $patch_lines
288 # We try again without 3-way-merging
289 $git->am( { "abort" => 1 } );
290 show_prg( sprintf("Applying %s (2nd try)", basename($pFile) ) );
295 # --- 3) Try to apply the patch without 3-way-merging ---
296 # ---------------------------------------------------------------
299 @lGitRes = $git->am( {
300 -STDIN => $patch_lines
304 $git->am( { "abort" => 1 } );
305 print "\nERROR: Couldn't apply $pFile\n";
306 print "Exit Code : " . $_->status . "\n";
307 print "Message : " . $_->error . "\n";
309 $result or return $result; ## Give up and exit
312 # --- 4) Get the new commit id, so we can update %hMutuals ---
313 # ---------------------------------------------------------------
314 $hMutuals{$upstream_path}{$wanted_refid}{tgt} = shorten_refid($WORKDIR, "HEAD");
315 length($hMutuals{$upstream_path}{$wanted_refid}{tgt}) or return 0; # Give up and exit
317 # The commit of the just applied patch file becomes the last mutual commit.
318 $hMutuals{$upstream_path}{$wanted_refid}{mutual}
319 = shorten_refid($upstream_path, $hSrcCommits{$pFile});
320 length($hMutuals{$upstream_path}{$wanted_refid}{mutual}) or return 0; # Give up and exit
323 } ## end sub apply_patch
325 # ------------------------------------------------------
326 # --- Build a hash of commits for the current hFile. ---
327 # ------------------------------------------------------
329 my $git = Git::Wrapper->new($upstream_path);
330 my @lRev = $git->rev_list( {
334 }, "${mutual_commit}..${wanted_refid}", $hFile->{src} );
336 for my $line (@lRev) {
337 if ( $line =~ m/^(\S+)\s+/ ) {
338 defined( $hCommits{$1} )
339 or ++$commit_count and $hCommits{$1} = 0;
342 } ## end for my $line (@lRev)
345 } ## end sub build_hCommits
347 # ------------------------------------------------------------------
348 # --- Build a list of the relevant commits in topological order. ---
349 # ------------------------------------------------------------------
351 my $git = Git::Wrapper->new($upstream_path);
353 my @lRev = $git->rev_list( {
357 }, "${mutual_commit}..${wanted_refid}" );
359 for my $line (@lRev) {
360 if ( $line =~ m/^(\S+)\s+/ ) {
361 defined( $hCommits{$1} )
362 and show_prg("Noting down $1")
363 and push @lCommits, "$1";
365 } ## end for my $line (@lRev)
369 } ## end sub build_lCommits
371 # ----------------------------------------------------------
372 # --- Add an entry to hFiles for a specific target file. ---
373 # ----------------------------------------------------------
377 defined($tgt) and length($tgt) or print("ERROR\n") and die("build_hfile: tgt is empty ???");
379 # We only prefixed './' to unify things. Now it is no longer needed:
382 # Check the target file
384 $src =~ s/elogind/systemd/g;
386 -f "$upstream_path/$src" or return 0;
388 # Build the patch name
392 # Build the central data structure.
394 patch => $output_path . "/" . $patch . ".patch",
399 # This is now our current hFile
400 $hFile = $hFiles{$tgt};
403 } ## end sub build_hFile
405 # ----------------------------------------------------------------
406 # --- Fill $output_path with formatted patches from @lCommits. ---
407 # ----------------------------------------------------------------
409 my $git = Git::Wrapper->new($upstream_path);
415 for my $refid (@lCommits) {
416 @lRev = $git->rev_list( { "1" => 1, oneline => 1 }, $refid );
418 show_prg( sprintf( "Building %03d: %s", ++$cnt, $lRev[0] ) );
421 @lPath = $git->format_patch(
425 "find-copies-harder" => 1,
427 "output-directory" => $output_path
429 "--start-number=$cnt",
433 print "\nERROR: Couldn't format-patch $refid\n";
434 print "Exit Code : " . $_->status . "\n";
435 print "Message : " . $_->error . "\n";
438 $result or return $result;
439 } ## end for my $refid (@lCommits)
440 $cnt and show_prg("") and print("$cnt patches built\n");
442 # Just a safe guard, that is almost guaranteed to never catch.
443 if ( $cnt != $commit_count ) {
444 print "ERROR: $commit_count patches expected, but only $cnt patches generated!\n";
449 } ## end sub build_lPatches
451 # -------------------------------------------------------
452 # --- Use check_tree.pl on the given commit and file. ---
453 # -------------------------------------------------------
455 my ( $commit, $file, $isNew ) = @_;
458 # If this is the creation of a new file, the hFile must be built.
460 my $tgt_file = basename($file);
461 my $tgt_dir = dirname($file);
462 $tgt_file =~ s/systemd/elogind/g;
464 defined( $hDirectories{$tgt_dir} )
465 or $tgt_dir =~ s/systemd/elogind/g;
467 my $tgt = "$tgt_dir/$tgt_file";
469 # Build the patch name
473 patch => $output_path . "/" . $patch . ".patch",
477 $stNew = "--create ";
479 my $path = $hFiles{$file}{patch};
481 # Now get the patch built
482 my @lResult = `$CHECK_TREE --stay -c $commit ${stNew}-f $file $upstream_path 2>&1`;
487 print "\n$CHECK_TREE died!\n";
490 print "failed to execute: $err\n";
491 } elsif ( $? & 127 ) {
492 printf "Signal %d, %s coredump\n", ( $? & 127 ), ( $? & 128 ) ? 'with' : 'without';
494 printf "child exited with value %d\n", $? >> 8;
496 print "-----\n" . join( "", @lResult ) . "-----\n";
500 # If check_tree found no diff or cleaned up all hunks, no patch is created.
501 for my $line (@lResult) {
503 if ( $line =~ m/${file}:\s+(clean|same)/ ) {
506 } ## end for my $line (@lResult)
509 } ## end sub check_tree
511 # -----------------------------------------------------------------------
512 # --- Checkout the given refid on $upstream_path ---
513 # --- Param 1 is the path where to do the checkout
514 # --- Param 2 is the refid to check out. ---
515 # --- Param 3 can be set to 1, if mutuals{src} and previous_refid ---
516 # --- shall be stored. ---
517 # --- Returns 1 on success, 0 otherwise. ---
518 # -----------------------------------------------------------------------
520 my ($path, $commit, $do_store) = @_;
522 # It is completely in order to not wanting to checkout a specific commit.
523 defined($commit) and length($commit) or return 1;
525 my $git = Git::Wrapper->new($path);
527 my $old_commit = shorten_refid($path, "HEAD");;
529 # The current commit must be valid:
530 length($old_commit) or return 0;
532 # Get the shortened commit hash of $commit
533 $new_commit = shorten_refid($path, $commit);
534 length($new_commit) or return 0;
536 # Now check it out, unless we are already there:
537 if ( $old_commit ne $new_commit ) {
539 print "Checking out $new_commit in ${path}...";
541 $git->checkout($new_commit);
543 print "\nERROR: Couldn't checkout \"new_commit\" in $path\n";
544 print "Exit Code : " . $_->status . "\n";
545 print "Message : " . $_->error . "\n";
548 $result or return $result;
550 } ## end if ( $previous_refid ne...)
552 # Save the commit hash of the wanted refid and the previous commit if wanted
554 $hMutuals{$path}{$wanted_refid}{src} = $new_commit;
555 $previous_refid = $old_commit;
559 } ## end sub checkout_upstream
561 # -----------------------------------------------------------------------
562 # --- Finds all relevant files and store them in @wanted_files ---
563 # --- Returns 1 on success, 0 otherwise. ---
564 # -----------------------------------------------------------------------
565 sub generate_file_list {
567 # Do some cleanup first. Just to be sure.
568 print "Cleaning up...";
570 `find -iname '*.orig' -or -iname '*.bak' -or -iname '*.rej' -or -iname '*~' -or -iname '*.gc??' | xargs rm -f`;
573 # The idea now is, that we use File::Find to search for files
574 # in all legal directories this program allows.
575 print "Find relevant files...";
576 for my $xDir ( "docs", "factory", "m4", "man", "shell-completion", "src", "tools" ) {
578 find( \&wanted, "$xDir" );
582 # There are also root files we need to check. Thanks to the usage of
583 # check_tree.pl to generate the later commit diffs, these can be safely
584 # added to our file list as well.
585 for my $xFile ( "Makefile", "Makefile.am", "TODO", "CODING_STYLE", "configure", ".mailmap", "LICENSE.GPL2", "meson_options.txt", "NEWS", "meson.build", "configure.ac", ".gitignore" ) {
586 -f "$xFile" and push @source_files, "./$xFile";
588 print " done - " . ( scalar @source_files ) . " files found\n";
592 or print("ERROR: No source files found? Where the hell are we?\n")
595 # Eventually we can add each directory to %hDirectories
596 for my $xFile (@source_files) {
597 my $xDir = dirname($xFile);
599 if ( length($xDir) > 1 ) {
600 defined( $hDirectories{$xDir} ) or $hDirectories{$xDir} = 1;
602 } ## end for my $xFile (@source_files)
605 } ## end sub generate_file_list
607 # ------------------------------------------------------------------------------
608 # --- Find or read the last mutual refid between this and the upstream tree. ---
609 # ------------------------------------------------------------------------------
610 sub get_last_mutual {
612 # No matter whether the commit is set or not, we need to read the
613 # commit file now, and write it back later if we have changes.
614 if ( -f $COMMIT_FILE ) {
615 if ( open my $fIn, "<", $COMMIT_FILE ) {
620 for my $line (@lLines) {
622 $line =~ m/^\s*#/ and next;
625 $line =~ m/^\s*$/ and next;
627 if ( $line =~ m/^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s*$/ ) {
634 # We mast be in the right branch or right tag!
635 checkout_tree($usp, $ref, 0) or return 0;
637 $hMutuals{$usp}{$ref} = {
638 mutual => shorten_refid($usp, $mut),
642 $src =~ m/^src-(\S+)$/ and $hMutuals{$usp}{$ref}{src} = shorten_refid($usp, $1);
643 $tgt =~ m/^tgt-(\S+)$/ and $hMutuals{$usp}{$ref}{tgt} = shorten_refid($WORKDIR, $1);
644 } ## end if ( $line =~ m/^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s*$/)
645 } ## end for my $line (@lLines)
647 print("ERROR: $COMMIT_FILE can not be read!\n$!\n");
651 # Make sure we are back at the wanted place in the upstream tree
652 checkout_tree($upstream_path, $wanted_refid, 0);
653 } ## end if ( -f $COMMIT_FILE )
655 # Note down that reading of any file is done.
658 # If this is already set, we are fine.
659 if ( length($mutual_commit) ) {
660 $hMutuals{$upstream_path}{$wanted_refid}{mutual} = shorten_refid($upstream_path, $mutual_commit);
661 length($hMutuals{$upstream_path}{$wanted_refid}{mutual}) or return 0;
664 # Now check against --advance and then set $mutual_commit accordingly.
665 if ( defined( $hMutuals{$upstream_path}{$wanted_refid} ) ) {
667 defined( $hMutuals{$upstream_path}{$wanted_refid}{src} )
668 and $hMutuals{$upstream_path}{$wanted_refid}{mutual}
669 = $hMutuals{$upstream_path}{$wanted_refid}{src}
670 or print "ERROR: --advance set, but no source hash found!\n" and return 0;
672 $mutual_commit = $hMutuals{$upstream_path}{$wanted_refid}{mutual};
674 } ## end if ( defined( $hMutuals...))
676 print "ERROR: There is no last mutual commit known for refid \"$wanted_refid\"!\n";
679 } ## end sub get_last_mutual
682 # ---------------------------------------------------------------------------
683 # --- Signal handler so we don't break without writing a new commit file. ---
684 # ---------------------------------------------------------------------------
687 print "\nCaught SIG${sig}!\n";
692 # -----------------------------------------------------------------------
693 # --- parse the given list for arguments. ---
694 # --- returns 1 on success, 0 otherwise. ---
695 # --- sets global $show_help to 1 if the long help should be printed. ---
696 # -----------------------------------------------------------------------
701 for ( my $i = 0 ; $i < @args ; ++$i ) {
703 # Check --advance option
704 if ( $args[$i] =~ m/^--advance$/ ) {
708 # Check for -c|--commit option
709 # -------------------------------------------------------------------------------
710 elsif ( $args[$i] =~ m/^-(?:c|-commit)$/ ) {
711 if ( ( ( $i + 1 ) >= @args )
712 || ( $args[ $i + 1 ] =~ m,^[-/.], ) )
714 print "ERROR: Option $args[$i] needs a refid as argument!\n\nUsage: $USAGE_SHORT\n";
717 } ## end if ( ( ( $i + 1 ) >= @args...))
718 $mutual_commit = $args[ ++$i ];
719 } ## end elsif ( $args[$i] =~ m/^-(?:c|-commit)$/)
721 # Check for -h|--help option
722 # -------------------------------------------------------------------------------
723 elsif ( $args[$i] =~ m/^-(?:h|-help)$/ ) {
727 # Check for -o|--output option
728 # -------------------------------------------------------------------------------
729 elsif ( $args[$i] =~ m/^-(?:o|-output)$/ ) {
730 if ( ( ( $i + 1 ) >= @args )
731 || ( $args[ $i + 1 ] =~ m,^[-/.], ) )
733 print "ERROR: Option $args[$i] needs a path as argument!\n\nUsage: $USAGE_SHORT\n";
736 } ## end if ( ( ( $i + 1 ) >= @args...))
737 $output_path = abs_path( $args[ ++$i ] );
738 } ## end elsif ( $args[$i] =~ m/^-(?:o|-output)$/)
740 # Check for unknown options:
741 # -------------------------------------------------------------------------------
742 elsif ( $args[$i] =~ m/^-/ ) {
743 print "ERROR: Unknown option \"$args[$i]\" encountered!\n\nUsage: $USAGE_SHORT\n";
747 # Everything else is considered to the path to upstream first and refid second
748 # -------------------------------------------------------------------------------
750 # But only if they are not set, yet:
751 if ( length($upstream_path) && length($wanted_refid) ) {
752 print "ERROR: Superfluous argument \"$args[$i]\" found!\n\nUsage: $USAGE_SHORT\n";
756 if ( length($upstream_path) ) {
757 $wanted_refid = "$args[$i]";
759 if ( !-d "$args[$i]" ) {
760 print "ERROR: Upstream path \"$args[$i]\" does not exist!\n\nUsage: $USAGE_SHORT\n";
764 $upstream_path = $args[$i];
765 } ## end else [ if ( length($upstream_path...))]
766 } ## end else [ if ( $args[$i] =~ m/^--advance$/)]
767 } ## End looping arguments
769 # If we have no refid now, show short help.
770 if ( $result && !$show_help && !length($wanted_refid) ) {
771 print "ERROR: Please provide a path to upstream and a refid!\n\nUsage: $USAGE_SHORT\n";
775 # If both --advance and --commit were used, we can not tell what the
776 # user really wants. So better be safe here, or we might screw the tree!
777 if ( $do_advance && length($mutual_commit) ) {
778 print "ERROR: You have used both --advance and --commit.\n";
779 print " Which one is the one you really want?\n\n";
780 print "Usage: $USAGE_SHORT\n";
782 } ## end if ( $do_advance && length...)
785 } ## parse_srgs() end
787 # --------------------------------------------------------------
788 # --- Use check_tree.pl to generate valid diffs on all valid ---
789 # --- files within the patch with the given number. ---
790 # --------------------------------------------------------------
795 if ( open( my $fIn, "<", $pFile ) ) {
800 print "\nERROR: $pFile could not be opened for reading!\n$!\n";
804 # Copy the header, ended by either '---' or 'diff '
805 # ----------------------------------------------------------
807 my $lCnt = scalar @lLines;
810 while ( $lCnt-- > 0 ) {
812 # Can not be done in while(), or empty lines would break the loop.
813 my $line = shift @lLines;
815 # We break this once we have found a file summary line
816 if ( $line =~ m/^\s+(\S+)\s+\|\s+\d+/ ) {
817 unshift @lLines, $line; ## Still needed
818 ++$lCnt; ## Yeah, put it up again!
822 # Before transfering the line, see if this is the commit info
823 $line =~ m/^From (\S+)\s\w{3}\s\w{3}\s\d{2}/ and $commit = $1;
826 } ## end while ( $lCnt-- > 0 )
828 # There is something wrong if we have no commit hash now
829 if ( 0 == length($commit) ) {
830 print "\nERROR: No 'From <hash>' line found!\n";
834 # There is something wrong if the next part is not a file summary line.
835 # ----------------------------------------------------------------------
836 if ( !defined( $lLines[0] ) || !( $lLines[0] =~ m/^\s+(\S+)\s+\|\s+\d+/ ) ) {
837 print "\nERROR: No file summary block found!\n";
838 print "The line currently looked at is:\n";
839 print "|" . ( defined( $lLines[0] ) ? $lLines[0] : "UNDEF" ) . "|\n";
840 print "We still have $lCnt lines to go.\n";
841 print "\nlOut so far:\n" . join( "\n", @lOut ) . "\n---------- END\n";
843 } ## end if ( !defined( $lLines...))
845 my @lFixedPatches = ();
846 while ( $lCnt-- > 0 ) {
847 my $isNew = 0; ## 1 if we hit a creation summary line
848 my $line = shift @lLines;
849 my $real = ""; ## The real file to work with
850 my $src = ""; ## Source in upstream
851 my $tgt = ""; ## Target in downstream
853 # This ends on the first empty line.
854 $line =~ m/^\s*$/ and push( @lOut, "" ) and last;
856 # This is either a line modification information, or a
857 # creation line of a new file. These look like this...
858 # src/shared/meson.build | 1 +
859 $line =~ m/^\s+(\S+)\s+\|.*/ and $src = $1;
862 # create mode 100644 src/network/netdev/wireguard.c
863 $line =~ m/^\s+create\s+mode\s+\d+\s+(\S+)\s*$/
867 # Otherwise it is the modification summary line
868 length($src) or push( @lOut, $line ) and next;
871 $tgt =~ s/systemd/elogind/g;
873 # The determination what is valid is different for whether this is
874 # the modification of an existing or the creation of a new file
876 defined( $hDirectories{ dirname($tgt) } ) and $real = $tgt or
877 defined( $hDirectories{ dirname($src) } ) and $real = $src;
879 # Try the renamed first, then the non-renamed
880 defined( $hFiles{$tgt} ) and $real = $tgt
881 or defined( $hFiles{$src} )
883 } ## end else [ if ($isNew) ]
885 # We neither need diffs on invalid files, nor new files in invalid directories.
886 length($real) or next;
888 # Now use $real to get the patch needed, if it is set.
889 my $pNew = check_tree( $commit, $real, $isNew );
891 # If no patch was generated, the file is either "same" or "clean".
892 $pNew eq "none" and next;
894 # However, an empty $pNew indicates an error. (check_tree() has it reported already.)
895 length($pNew) and push @lFixedPatches, $pNew or return 0;
897 # If we are here, transfer the file line. It is useful.
898 $line =~ s/$src/$real/;
900 } ## End of scanning lines
902 if ( 0 == scalar @lFixedPatches) {
903 unlink $pFile; ## Empty patch...
907 # Load all generated patches and add them to lOut
908 # ----------------------------------------------------------
909 for my $pNew (@lFixedPatches) {
910 if ( open my $fIn, "<", $pNew ) {
917 print "\nERROR: Can't open $pNew for reading!\n$!\n";
920 } ## end for my $pNew (@lFixedPatches)
922 # Store the patch commit for later reference
923 # ----------------------------------------------------------
924 $hSrcCommits{$pFile} = $commit;
926 # Eventually overwrite $pFile with @lOut
927 # ----------------------------------------------------------
928 if ( open( my $fOut, ">", $pFile ) ) {
929 print $fOut join( "\n", @lOut ) . "\n";
932 print "\nERROR: Can not opne $pFile for writing!\n$!\n";
937 } ## end sub rework_patch
940 # --------------------------------------------
941 # --- Write back %hMutuals to $COMMIT_FILE ---
942 # --------------------------------------------
943 sub set_last_mutual {
945 # Don't do anything if we haven't finished reading the commit file:
946 $commits_read or return 1;
948 my $out_text = "# Automatically generated commit information\n"
949 . "# Only edit if you know what these do!\n\n";
950 my ($pLen, $rLen, $mLen, $sLen) = (0, 0, 0, 0); # Length for the fmt
952 # First we need a length to set all fields to.
953 # ---------------------------------------------------------------
954 # (And build a shortcut while at it so we do ...
955 for my $path (sort keys %hMutuals) {
956 length($path) > $pLen and $pLen = length($path);
957 for my $refid (sort keys %{$hMutuals{$path}}) {
958 my $hM = $hMutuals{$path}{$refid}; # Shortcut!
959 length($refid) > $rLen and $rLen = length($refid);
960 length($hM->{mutual}) > $mLen and $mLen = length($hM->{mutual});
961 defined($hM->{src}) and (length($hM->{src}) > 4)
962 and $hM->{src} = "src-" . $hM->{src}
964 length($hM->{src}) > $sLen and $sLen = length($hM->{src});
965 defined($hM->{tgt}) and (length($hM->{tgt}) > 4)
966 and $hM->{tgt} = "tgt-" . $hM->{tgt}
971 # Now we can build the fmt
972 my $out_fmt = sprintf("%%-%ds %%-%ds %%-%ds %%-%ds %%s\n", $pLen, $rLen, $mLen, $sLen);
974 # Second we build the out text
975 # ---------------------------------------------------------------
976 for my $path (sort keys %hMutuals) {
977 for my $refid (sort keys %{$hMutuals{$path}}) {
978 my $hM = $hMutuals{$path}{$refid}; # Shortcut!
979 $out_text .= sprintf($out_fmt,
989 # Third, write a new $COMMIT_FILE
990 # ---------------------------------------------------------------
991 if (open(my $fOut, ">", $COMMIT_FILE)) {
992 print $fOut $out_text;
995 print "ERROR: Can not open $COMMIT_FILE for writing!\n$!\n";
996 print "The content would have been:\n" . ('-' x 24) . "\n$out_text" . ('-' x 24) . "\n";
1004 # -------------------------------------------------------------------------
1005 # --- Take DIR and REFID and return the shortest possible REFID in DIR. ---
1006 # -------------------------------------------------------------------------
1010 defined($p) and length($p) or die("shorten_refid() called with undef path!");
1011 defined($r) and length($r) or die("shorten_refid() called with undef refid!");
1013 my $git = Git::Wrapper->new($p);
1017 # Get the shortest possible $r (REFID)
1019 @lOut = $git->rev_parse( { short => 1 }, "$r" );
1021 print "ERROR: Couldn't rev-parse ${p}::${r}\n";
1022 print "Exit Code : " . $_->status . "\n";
1023 print "Message : " . $_->error . "\n";
1026 $result and return $lOut[0];
1030 # Helper to show the argument as a non permanent progress line.
1033 my $len = length($prg_line);
1035 $len and print "\r" . ( ' ' x $len ) . "\r";
1039 if ( length($prg_line) ) {
1045 } ## end sub show_prg
1047 # Callback function for File::Find
1049 my $f = $File::Find::name;
1051 $f =~ m,^\./, or $f = "./$f";
1054 and ( !( $_ =~ m/\.pwx$/ ) )
1055 and push @source_files, $File::Find::name;