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