Change naming of local patch-related branches and unify
[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/\Q$master_branch\E/(.*)#o) {
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/$master_branch/$name";
28         system 'git', 'branch', '-D', $branch and exit 1;
29     }
30     %local_branch = ( );
31 }
32
33 require 'packaging/git-status.pl';
34 check_git_state($master_branch, !$skip_branch_check, 1);
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 }