Commit | Line | Data |
---|---|---|
06886d36 WD |
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 | } |