patch-update 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. #!/usr/bin/perl
  2. # This script is used to turn one or more of the "patch/BASE/*" branches
  3. # into one or more diffs in the "patches" directory. Pass the option
  4. # --gen if you want generated files in the diffs. Pass the name of
  5. # one or more diffs if you want to just update a subset of all the
  6. # diffs.
  7. use strict;
  8. use warnings;
  9. use Getopt::Long;
  10. my $patches_dir = 'patches';
  11. my $tmp_dir = "patches.$$";
  12. my $make_gen_cmd = 'make -f prepare-source.mak conf && ./config.status && make gen';
  13. &Getopt::Long::Configure('bundling');
  14. &usage if !&GetOptions(
  15. 'branch|b=s' => \( my $master_branch = 'master' ),
  16. 'skip-check' => \( my $skip_branch_check ),
  17. 'shell|s' => \( my $launch_shell ),
  18. 'gen:s' => \( my $incl_generated_files ),
  19. 'help|h' => \( my $help_opt ),
  20. );
  21. &usage if $help_opt;
  22. if (defined $incl_generated_files) {
  23. $patches_dir = $incl_generated_files if $incl_generated_files ne '';
  24. $incl_generated_files = 1;
  25. }
  26. die "No '$patches_dir' directory was found.\n" unless -d $patches_dir;
  27. die "No '.git' directory present in the current dir.\n" unless -d '.git';
  28. require 'packaging/git-status.pl';
  29. check_git_state($master_branch, !$skip_branch_check, 1);
  30. my $master_commit;
  31. open PIPE, '-|', "git log -1 --no-color $master_branch" or die $!;
  32. while (<PIPE>) {
  33. if (/^commit (\S+)/) {
  34. $master_commit = $1;
  35. last;
  36. }
  37. }
  38. close PIPE;
  39. die "Unable to determine commit hash for master branch: $master_branch\n" unless defined $master_commit;
  40. my @extra_files;
  41. open(IN, '<', 'Makefile.in') or die "Couldn't open Makefile.in: $!\n";
  42. while (<IN>) {
  43. if (s/^GENFILES=//) {
  44. while (s/\\$//) {
  45. $_ .= <IN>;
  46. }
  47. @extra_files = split(' ', $_);
  48. last;
  49. }
  50. }
  51. close IN;
  52. if ($incl_generated_files) {
  53. die "'$tmp_dir' must not exist in the current directory.\n" if -e $tmp_dir;
  54. mkdir($tmp_dir, 0700) or die "Unable to mkdir($tmp_dir): $!\n";
  55. system "$make_gen_cmd && rsync -a @extra_files $tmp_dir/master/" and exit 1;
  56. }
  57. our $last_touch = time;
  58. my %patches;
  59. # Start by finding all patches so that we can load all possible parents.
  60. open(PIPE, '-|', 'git', 'branch', '-l') or die $!;
  61. while (<PIPE>) {
  62. if (m# patch/\Q$master_branch\E/(.*)#o) {
  63. $patches{$1} = 1;
  64. }
  65. }
  66. close PIPE;
  67. my @patches = sort keys %patches;
  68. my(%parent, %description);
  69. foreach my $patch (@patches) {
  70. my $branch = "patch/$master_branch/$patch";
  71. my $desc = '';
  72. open(PIPE, '-|', 'git', 'diff', '-U1000', "$master_branch...$branch", '--', "PATCH.$patch") or die $!;
  73. while (<PIPE>) {
  74. last if /^@@ /;
  75. }
  76. while (<PIPE>) {
  77. next unless s/^[ +]//;
  78. if (m#patch -p1 <patches/(\S+)\.diff# && $1 ne $patch) {
  79. my $parent = $parent{$patch} = $1;
  80. if (!$patches{$parent}) {
  81. die "Parent of $patch is not a local branch: $parent\n";
  82. }
  83. }
  84. $desc .= $_;
  85. }
  86. close PIPE;
  87. $description{$patch} = $desc;
  88. }
  89. if (@ARGV) {
  90. # Limit the list of patches to actually process based on @ARGV.
  91. @patches = ( );
  92. foreach (@ARGV) {
  93. s{^patch(es)?/} {};
  94. s{\.diff$} {};
  95. if (!$patches{$_}) {
  96. die "Local branch not available for patch: $_\n";
  97. }
  98. push(@patches, $_);
  99. }
  100. }
  101. my %completed;
  102. foreach my $patch (@patches) {
  103. next if $completed{$patch}++;
  104. last unless update_patch($patch);
  105. }
  106. if ($incl_generated_files) {
  107. system "rm -rf $tmp_dir";
  108. }
  109. sleep 1 while $last_touch >= time;
  110. system "git checkout $master_branch" and exit 1;
  111. exit;
  112. sub update_patch
  113. {
  114. my($patch) = @_;
  115. my $parent = $parent{$patch};
  116. my $based_on;
  117. if (defined $parent) {
  118. unless ($completed{$parent}++) {
  119. update_patch($parent);
  120. }
  121. $based_on = $parent = "patch/$master_branch/$parent";
  122. } else {
  123. $parent = $master_branch;
  124. $based_on = $master_commit;
  125. }
  126. print "======== $patch ========\n";
  127. sleep 1 while $incl_generated_files && $last_touch >= time;
  128. system "git checkout patch/$master_branch/$patch" and return 0;
  129. my $ok = system("git merge $based_on") == 0;
  130. if (!$ok || $launch_shell) {
  131. my($parent_dir) = $parent =~ m{([^/]+)$};
  132. print qq|"git merge $based_on" incomplete -- please fix.\n| if !$ok;
  133. $ENV{PS1} = "[$parent_dir] $patch: ";
  134. while (1) {
  135. if (system($ENV{SHELL}) != 0) {
  136. print "Abort? [n/y] ";
  137. $_ = <STDIN>;
  138. next unless /^y/i;
  139. return 0;
  140. }
  141. my($cur_branch, $is_clean, $status) = check_git_status(0);
  142. last if $is_clean;
  143. print $status;
  144. }
  145. }
  146. open(OUT, '>', "$patches_dir/$patch.diff") or die $!;
  147. print OUT $description{$patch}, "\nbased-on: $based_on\n";
  148. if ($incl_generated_files) {
  149. system "$make_gen_cmd && rsync -a @extra_files $tmp_dir/$patch/" and exit 1;
  150. }
  151. $last_touch = time;
  152. open(PIPE, '-|', 'git', 'diff', $based_on) or die $!;
  153. DIFF: while (<PIPE>) {
  154. while (m{^diff --git a/PATCH}) {
  155. while (<PIPE>) {
  156. last if m{^diff --git a/};
  157. }
  158. last DIFF if !defined $_;
  159. }
  160. next if /^index /;
  161. print OUT $_;
  162. }
  163. close PIPE;
  164. if ($incl_generated_files) {
  165. my $parent_dir;
  166. if ($parent eq $master_branch) {
  167. $parent_dir = 'master';
  168. } else {
  169. ($parent_dir) = $parent =~ m{([^/]+)$};
  170. }
  171. open(PIPE, '-|', 'diff', '-up', "$tmp_dir/$parent_dir", "$tmp_dir/$patch") or die $!;
  172. while (<PIPE>) {
  173. s#^(diff -up) $tmp_dir/[^/]+/(.*?) $tmp_dir/[^/]+/(.*)#$1 a/$2 b/$3#o;
  174. s#^\Q---\E $tmp_dir/[^/]+/([^\t]+)\t.*#--- a/$1#o;
  175. s#^\Q+++\E $tmp_dir/[^/]+/([^\t]+)\t.*#+++ b/$1#o;
  176. print OUT $_;
  177. }
  178. close PIPE;
  179. }
  180. close OUT;
  181. 1;
  182. }
  183. exit;
  184. sub usage
  185. {
  186. die <<EOT;
  187. Usage: patch-update [OPTIONS] [patches/DIFF...]
  188. Options:
  189. -b, --branch=BRANCH The master branch to merge into the patch/BASE/* branches.
  190. --gen[=DIR] Include generated files. Optional destination DIR
  191. arg overrides the default of using the "patches" dir.
  192. --skip-check Skip the check that ensures starting with a clean branch.
  193. -s, --shell Launch a shell for every patch/BASE/* branch updated, not
  194. just when a conflict occurs.
  195. -h, --help Output this help message.
  196. EOT
  197. }