| 1 | #!/usr/bin/perl |
| 2 | |
| 3 | use strict; |
| 4 | use warnings; |
| 5 | use Getopt::Long; |
| 6 | |
| 7 | &Getopt::Long::Configure('bundling'); |
| 8 | &usage if !&GetOptions( |
| 9 | 'branch|b=s' => \( my $master_branch = 'master' ), |
| 10 | 'skip-check' => \( my $skip_branch_check ), |
| 11 | 'delete' => \( my $delete_local_branches ), |
| 12 | 'help|h' => \( my $help_opt ), |
| 13 | ); |
| 14 | &usage if $help_opt; |
| 15 | |
| 16 | require 'packaging/git-status.pl'; |
| 17 | check_git_state($master_branch, !$skip_branch_check, 1); |
| 18 | |
| 19 | my %local_branch; |
| 20 | open PIPE, '-|', 'git branch -l' or die "Unable to fork: $!\n"; |
| 21 | while (<PIPE>) { |
| 22 | if (m# patch/\Q$master_branch\E/(.*)#o) { |
| 23 | $local_branch{$1} = 1; |
| 24 | } |
| 25 | } |
| 26 | close PIPE; |
| 27 | |
| 28 | if ($delete_local_branches) { |
| 29 | foreach my $name (sort keys %local_branch) { |
| 30 | my $branch = "patch/$master_branch/$name"; |
| 31 | system 'git', 'branch', '-D', $branch and exit 1; |
| 32 | } |
| 33 | %local_branch = ( ); |
| 34 | } |
| 35 | |
| 36 | my @patch_list; |
| 37 | foreach (@ARGV) { |
| 38 | if (!-f $_) { |
| 39 | die "File not found: $_\n"; |
| 40 | } |
| 41 | die "Filename is not a .diff file: $_\n" unless /\.diff$/; |
| 42 | push @patch_list, $_; |
| 43 | } |
| 44 | |
| 45 | exit unless @patch_list; |
| 46 | |
| 47 | my(%scanned, %created, %info); |
| 48 | |
| 49 | foreach my $patch (@patch_list) { |
| 50 | my($where, $name) = $patch =~ m{^(.*?)([^/]+)\.diff$}; |
| 51 | next if $scanned{$name}++; |
| 52 | |
| 53 | open IN, '<', $patch or die "Unable to open $patch: $!\n"; |
| 54 | |
| 55 | my $info = ''; |
| 56 | my $commit; |
| 57 | while (<IN>) { |
| 58 | if (m#^based-on: (\S+)#) { |
| 59 | $commit = $1; |
| 60 | last; |
| 61 | } |
| 62 | last if m#^index .*\.\..* \d#; |
| 63 | last if m#^diff --git #; |
| 64 | last if m#^--- (old|a)/#; |
| 65 | $info .= $_; |
| 66 | } |
| 67 | close IN; |
| 68 | |
| 69 | $info =~ s/\s+\Z/\n/; |
| 70 | |
| 71 | my $parent = $master_branch; |
| 72 | my @patches = $info =~ m#patch -p1 <patches/(\S+)\.diff#g; |
| 73 | if (@patches) { |
| 74 | if ($patches[-1] eq $name) { |
| 75 | pop @patches; |
| 76 | } else { |
| 77 | warn "No identity patch line in $patch\n"; |
| 78 | } |
| 79 | if (@patches) { |
| 80 | $parent = pop @patches; |
| 81 | if (!$scanned{$parent}) { |
| 82 | unless (-f "$where$parent.diff") { |
| 83 | die "Unknown parent of $patch: $parent\n"; |
| 84 | } |
| 85 | # Add parent to @patch_list so that we will look for the |
| 86 | # parent's parent. Any duplicates will just be ignored. |
| 87 | push @patch_list, "$where$parent.diff"; |
| 88 | } |
| 89 | } |
| 90 | } else { |
| 91 | warn "No patch lines found in $patch\n"; |
| 92 | } |
| 93 | |
| 94 | $info{$name} = [ $parent, $info, $commit ]; |
| 95 | } |
| 96 | |
| 97 | foreach my $patch (@patch_list) { |
| 98 | create_branch($patch); |
| 99 | } |
| 100 | |
| 101 | system 'git', 'checkout', $master_branch and exit 1; |
| 102 | |
| 103 | exit; |
| 104 | |
| 105 | sub create_branch |
| 106 | { |
| 107 | my($patch) = @_; |
| 108 | my($where, $name) = $patch =~ m{^(.*?)([^/]+)\.diff$}; |
| 109 | |
| 110 | return if $created{$name}++; |
| 111 | |
| 112 | my $ref = $info{$name}; |
| 113 | my($parent, $info, $commit) = @$ref; |
| 114 | |
| 115 | my $parent_branch; |
| 116 | if ($parent eq $master_branch) { |
| 117 | $parent_branch = $master_branch; |
| 118 | $parent_branch = $commit if defined $commit; |
| 119 | } else { |
| 120 | create_branch("$where/$parent.diff"); |
| 121 | $parent_branch = "patch/$master_branch/$parent"; |
| 122 | } |
| 123 | |
| 124 | my $branch = "patch/$master_branch/$name"; |
| 125 | print "\n", '=' x 64, "\nProcessing $branch ($parent_branch)\n"; |
| 126 | |
| 127 | if ($local_branch{$name}) { |
| 128 | system 'git', 'branch', '-D', $branch and exit 1; |
| 129 | } |
| 130 | |
| 131 | system 'git', 'checkout', '-b', $branch, $parent_branch and exit 1; |
| 132 | |
| 133 | open OUT, '>', "PATCH.$name" or die $!; |
| 134 | print OUT $info; |
| 135 | close OUT; |
| 136 | system 'git', 'add', "PATCH.$name" and exit 1; |
| 137 | |
| 138 | open IN, '<', $patch or die "Unable to open $patch: $!\n"; |
| 139 | $_ = join('', <IN>); |
| 140 | close IN; |
| 141 | |
| 142 | open PIPE, '|-', 'patch -p1' or die $!; |
| 143 | print PIPE $_; |
| 144 | close PIPE; |
| 145 | |
| 146 | system 'rm -f *.orig */*.orig'; |
| 147 | |
| 148 | while (m#\nnew file mode (\d+)\s+--- /dev/null\s+\Q+++\E b/(.*)#g) { |
| 149 | chmod oct($1), $2; |
| 150 | system 'git', 'add', $2; |
| 151 | } |
| 152 | |
| 153 | while (1) { |
| 154 | system 'git status'; |
| 155 | print 'Press Enter to commit, Ctrl-C to abort, or type a wild-name to add a new file: '; |
| 156 | $_ = <STDIN>; |
| 157 | last if /^$/; |
| 158 | chomp; |
| 159 | system "git add $_"; |
| 160 | } |
| 161 | |
| 162 | while (system 'git', 'commit', '-a', '-m', "Creating branch from $name.diff.") { |
| 163 | exit 1 if system '/bin/zsh'; |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | sub usage |
| 168 | { |
| 169 | die <<EOT; |
| 170 | Usage branch-from-patch [OPTIONS] patches/DIFF... |
| 171 | |
| 172 | Options: |
| 173 | -b, --branch=BRANCH Create branches relative to BRANCH if no "based-on" |
| 174 | header was found in the patch file. |
| 175 | --skip-check Skip the check that ensures starting with a clean branch. |
| 176 | --delete Delete all the local patch/BASE/* branches, not just the ones |
| 177 | that are being recreated. |
| 178 | -h, --help Output this help message. |
| 179 | EOT |
| 180 | } |