source: subversion/applications/utils/revert/complex_revert.pl @ 30254

Last change on this file since 30254 was 30199, checked in by frederik, 6 years ago

script for reverting a lot of stuff at once

File size: 8.4 KB
Line 
1#!/usr/bin/perl
2
3# This program attempts to revert the sum of all edits in all
4# .osc data read from stdin; typically, you would prepare a
5# directory containing all changesets of one user and then do
6#
7# cat *.osc | perl complex_revert.pl
8#
9# The order on input is not relevant. Objects for which there's
10# a "create" in the input will be deleted unless there's also a
11# "delete" in the input. Objects modified in the input will be
12# reset to one version before the first modification. Objects
13# deleted in the input will also be reset similarly.
14
15# This script will automatically open revert changesets.
16
17# It will not revert objects where the newest version found on
18# input is not current anymore (i.e. objects that have been
19# modified in a changeset not given on input.
20
21# Also, where the script decides to undelete something or revert
22# something to an earlier state, and this is not possible because
23# some of the member objects required have been deleted meanwhile,
24# in changesets other than those given on input, the script will
25# not attempt to undelete these members.
26
27# Written by Frederik Ramm <frederik@remote.org>, public domain.
28
29use Changeset;
30use OsmApi;
31
32use strict;
33
34my $comment = "changeset comment goes here";
35my $revert_type = "top_down"; # or bottom_up - see comments in code below
36
37# no user servicable parts below
38
39my $mode;
40my $operation;
41my $restore;
42my $delete;
43my $done;
44my $current_cs;
45my $current_count;
46
47die unless ($revert_type eq 'top_down' || $revert_type eq 'bottom_up');
48
49open(LOG, "complex_revert.log");
50while(<LOG>)
51{
52   my ($o, $i, $r) = split(/ /, $_);
53   $done->{$o}->{$i} = 1;
54}
55close(LOG);
56
57printf STDERR "%d object IDs read from complex_revert.log - will not touch these again\n", 
58    scalar(keys(%$done));
59
60open(LOG, ">> complex_revert.log");
61
62while(<>)
63{
64    if (/<(create|modify|delete)>/)
65    {
66        $mode = $1;
67    }
68    elsif (/<(node|way|relation).*\sid="(\d+)"/)
69    {
70        my ($what, $id) = ($1, $2);
71        /version="(\d+)"/;
72        my $v = $1;
73        $operation->{$what}->{$id}->{$v} = $mode;
74    }
75}
76
77foreach my $object(qw/node way relation/)
78{
79    foreach my $id(keys %{$operation->{$object}})
80    {
81        my $firstop;
82        my $lastop;
83        my @k = sort(keys(%{$operation->{$object}->{$id}}));
84        my $firstv = shift @k;
85        my $lastv = pop @k;
86        $lastv = $firstv if (!defined $lastv);
87        my $firstop = $operation->{$object}->{$id}->{$firstv};
88        my $lastop = $operation->{$object}->{$id}->{$lastv};
89
90        if ($lastop eq "delete")
91        {
92            if ($firstop eq "create")
93            {
94                # ignore
95            }
96            else
97            {
98                $firstv--;
99                $restore->{$object}->{$id} =  "$firstv/$lastv";
100            }
101        }
102        elsif ($firstop eq "create")
103        {
104            $delete->{$object}->{$id} =  "$lastv";
105        }
106        else 
107        {
108            $firstv--;
109            $restore->{$object}->{$id} =  "$firstv/$lastv";
110        }
111    }
112}
113
114if ($revert_type eq 'bottom_up') 
115{
116    revert_bottom_up();
117}
118else
119{
120    revert_top_down(); 
121}
122
123handle_delete_soft();
124
125# revert_bottom_up is the simple method of reverting stuff - first,
126# all nodes are reverted (which may include undeleting), then all ways,
127# then all relations. The disadvantage of this is that if you have
128# 10k delete ways with 100k deleted nodes, the whole process might take
129# some time, and by the time you start undeleting ways, some mapper
130# might have cleaned up your orphan nodes already.
131
132sub revert_bottom_up
133{
134    $current_cs = Changeset::create($comment);
135    $current_count = 0;
136    foreach my $object(qw/node way relation/)
137    {
138        foreach my $id(keys %{$restore->{$object}})
139        {
140            my ($firstv, $lastv) = split("/", $restore->{$object}->{$id});
141            my $resp = OsmApi::get("$object/$id/$firstv");
142            if (!$resp->is_success)
143            {
144                print STDERR "cannot restore $object $id to version $firstv (get): ".$resp->status_line."\n";
145                print LOG "$object $id ERR GET ".$resp->code." ".$resp->status_line."\n";
146                next;
147            }
148            my $xml = $resp->content;
149            $xml =~ s/changeset="\d+"/changeset="$current_cs"/;
150            $xml =~ s/version="$firstv"/version="$lastv"/;
151            $xml =~ s/visible="no"//;
152            $resp = OsmApi::put("$object/$id", $xml);
153            if (!$resp->is_success)
154            {
155                print STDERR "cannot restore $object $id to version $firstv (put): ".$resp->status_line."\n";
156                my $b = $resp->content;
157                $b =~ s/\s+/ /g;
158                print LOG "$object $id ERR PUT ".$resp->status_line." $b\n";
159                next;
160            }
161            print LOG "$object $id OK revert to v$firstv\n";
162
163            if ($current_count++ > 40000)
164            {
165                Changeset::close($current_cs, $comment);
166                $current_cs = Changeset::create($comment);
167                $current_count = 0;
168            }
169        }
170    }
171}
172
173# revert_top_down attempts to process relation by relation and way by way,
174# undeleting all child objects each time
175
176sub revert_top_down
177{
178    $current_cs = Changeset::create($comment);
179    print LOG "changeset $current_cs created\n";
180    $current_count = 0;
181    foreach my $object(qw/relation way node/)
182    {
183        foreach my $id(keys %{$restore->{$object}})
184        {
185            next if defined($done->{$object}->{$id});
186            my ($firstv, $lastv) = split("/", $restore->{$object}->{$id});
187            revert_top_down_recursive($object, $id, $firstv, $lastv);
188        }
189    }
190}
191
192sub revert_top_down_recursive
193{
194    my ($object, $id, $firstv, $lastv) = @_;
195
196    my $resp = OsmApi::get("$object/$id/$firstv");
197    if (!$resp->is_success)
198    {
199        print STDERR "cannot restore $object $id to version $firstv (get): ".$resp->status_line."\n";
200        print LOG "$object $id ERR GET ".$resp->status_line."\n";
201        return;
202    }
203
204    my $xml = $resp->content;
205    foreach (split(/\n/, $xml))
206    { 
207        if (/<nd ref=.(\d+)/)
208        {
209            if (defined($restore->{'node'}->{$1}) && !defined($done->{'node'}->{$1}))
210            {
211                my ($fv, $lv) = split("/", $restore->{'node'}->{$1});
212                revert_top_down_recursive('node', $1, $fv, $lv);
213            }
214        }
215        elsif (/<member.*type=.(way|node|relation).*ref=.(\d+)/)
216        {
217            if (defined($restore->{$1}->{$2}) && !defined($done->{$1}->{$2}))
218            {
219                my ($fv, $lv) = split("/", $restore->{$1}->{$2});
220                revert_top_down_recursive($1, $2, $fv, $lv);
221            }
222        }
223    }
224
225    $xml =~ s/changeset="\d+"/changeset="$current_cs"/;
226    $xml =~ s/version="$firstv"/version="$lastv"/;
227    $xml =~ s/visible="no"//;
228    $resp = OsmApi::put("$object/$id", $xml);
229    if (!$resp->is_success)
230    {
231        print STDERR "cannot restore $object $id to version $firstv (put): ".$resp->status_line."\n";
232        my $b = $resp->content;
233        $b =~ s/\s+/ /g;
234        print LOG "$object $id ERR PUT ".$resp->status_line." $b\n";
235        return;
236    }
237    print LOG "$object $id OK revert to v$firstv\n";
238
239    # do this even on error, since retrying is no use?
240    $done->{$object}->{$id} = 1;
241
242    if ($current_count++ > 40000)
243    {
244        Changeset::close($current_cs, $comment);
245        print LOG "changeset $current_cs created\n";
246        $current_cs = Changeset::create($comment);
247        $current_count = 0;
248    }
249    return;
250}
251
252sub handle_delete_soft
253{
254    foreach my $object(qw/relation way node/)
255    {
256        foreach (@{$delete->{$object}})
257        {
258            my ($id, $lastv) = split("/");
259            my $xml = "<osm generator=\"osmtools\"><$object id=\"$id\" version=\"$lastv\" lat=\"0\" lon=\"0\" changeset=\"$current_cs\" /></osm>";
260            my $resp = OsmApi::delete("$object/$id", $xml);
261            if (!$resp->is_success)
262            {
263                print STDERR "cannot delete $object $id: ".$resp->status_line."\n";
264                my $b = $resp->content;
265                $b =~ s/\s+/ /g;
266                print LOG "$object $id ERR DELETE ".$resp->status_line." $b\n";
267                next;
268            }
269            print LOG "$object $id OK delete\n";
270
271            if ($current_count++ > 40000)
272            {
273                Changeset::close($current_cs, $comment);
274                $current_cs = Changeset::create($comment);
275                print LOG "changeset $current_cs created\n";
276                $current_count = 0;
277            }
278        }
279    }
280}
281
Note: See TracBrowser for help on using the repository browser.