source: subversion/applications/utils/revert/Revert.pm @ 29923

Last change on this file since 29923 was 17935, checked in by frederik, 10 years ago

bugfix for "seen" mechanism, and capability to read change files from stdin

File size: 5.8 KB
Line 
1#!/usr/bin/perl
2
3# Revert.pm
4# ---------
5#
6# Implements whole changeset reverts
7#
8# Part of the "osmtools" suite of programs
9# Originally written by Frederik Ramm <frederik@remote.org>; public domain
10
11package Revert;
12
13use strict;
14use warnings;
15
16use OsmApi;
17use Undo;
18
19# downloads a changeset and attempts to undo all changes
20# within that. currently transaction-based, so the revert will
21# fail if it cannot be done cleanly, but see variable $transaction.
22#
23# parameters:
24#   $undo_changeset: the changeset to nuke
25#   $changeset: the changeset in which the undo happens (must be open)
26# return:
27#   success=1 failure=undef
28
29sub revert
30{
31    my ($undo_changeset, $changeset) = @_;
32
33    my $osc;
34    if ($undo_changeset =~ /<osmChange/)
35    {
36        $osc = $undo_changeset;
37        $osc =~ /changeset="([^"]*)"/s or die "given osmChange document does not contain a changeset id";
38        $undo_changeset = $1;
39        print "reverting changes from changeset $undo_changeset\n";
40    }
41    else
42    {
43        my $resp = OsmApi::get("changeset/$undo_changeset/download");
44        if (!$resp->is_success)
45        {
46            print STDERR "changeset $undo_changeset cannot be retrieved: ".$resp->status_line."\n";
47            return undef;
48        }
49        $osc = $resp->content();
50    }
51
52    my $objects = {};
53    my $action;
54    my $seen = {};
55
56    foreach (split(/\n/, $osc))
57    { 
58        if (/<(modify|create|delete)/)
59        {
60            $action = $1;
61        }
62        elsif (/<(node|way|relation).*\sid=["'](\d+)["']/)
63        {
64            $seen->{$1.$2}++;
65            # if an object appears for a second time, ignore it here.
66            # but still count that it appeared twice.
67            next if ($seen->{$1.$2} > 1);
68            unshift(@{$objects->{"$action $1"}}, $2);
69        }
70    }
71
72    # first undelete nodes, ways, relations;
73    # then undo changes to nodes, ways, relations;
74    # then undo creations of relations, ways, nodes (note order).
75
76    my $success = [];
77    my $failure = [];
78    # set this to 0 if you want individual API requests rather than a changeset
79    # upload. this will be much slower but may be required if you cannot get all
80    # changes through due to problems.
81    my $transaction = 0;
82    # set this to 1 if you have a large number of object creations. this will
83    # bypass requesting object history for those, and simply try and delete them.
84    # which will fail if the object has been modified since.
85    my $delete_shortcut = 0;
86    my $oscpart;
87
88    foreach my $operation("delete node", "delete way", "delete relation",
89        "modify node", "modify way", "modify relation", 
90        "create relation", "create way", "create node")
91    {
92        printf("operation: $operation\n");
93
94        foreach my $object(@{$objects->{$operation}})
95        {
96            my ($what, $objtype) = split(/ /, $operation);
97            # this collects all undos in one osc document.
98            if ($transaction)
99            {
100                # the delete shortcut is an optimisation where we don't
101                # retrieve the object history. we can only do this if the
102                # object has been created and not further modified in this
103                # changeset.
104                if (($delete_shortcut) && ($what eq "create") && $seen->{$objtype.$object} == 1)
105                {
106
107                    print STDERR "$objtype $object created; shortcut deletion\n";
108                    $oscpart->{"delete"} .= "<$objtype id=\"$object\" lat=\"0\" lon=\"0\" version=\"1\" changeset=\"$changeset\" />\n";
109                }
110                # apart from the delete shortcut, we simply retrieve the
111                # object history and see what we have to do to take the
112                # object back to where it was before this changeset.
113                else
114                {
115                    my ($action, $xml) = Undo::determine_undo_action($objtype, $object, undef, $undo_changeset, $changeset);
116                    return undef unless (defined($action));
117                    $oscpart->{$action} .= $xml;
118                }
119            }
120            # this creates individual undo operations. currently unused!
121            else
122            {
123                if (($delete_shortcut) && ($what eq "create") && $seen->{$objtype.$object} == 1)
124                {
125                    print STDERR "$objtype $object created; shortcut deletion\n";
126                    my $resp = OsmApi::delete("$objtype/$object", "<osm version='0.6'><$objtype id=\"$object\" lat=\"0\" lon=\"0\" version=\"1\" changeset=\"$changeset\" /></osm>");
127                    if (!$resp->is_success)
128                    {
129                        push(@$failure, "$operation $object");
130                    }
131                    else
132                    {
133                        push(@$success, "$operation $object");
134                    }
135                }
136                else
137                {
138                    if (Undo::undo($objtype, $object, undef, $undo_changeset, $changeset))
139                    {
140                        push(@$success, "$operation $object");
141                    }
142                    else
143                    {
144                        push(@$failure, "$operation $object");
145                    }
146                }
147            }
148        }
149    }
150
151    if ($transaction)
152    {
153        my $osc = "<osmChange version='0.6' generator='osmtools'>\n";
154        foreach my $action("modify", "create", "delete")
155        {
156            if (defined($oscpart->{$action}))
157            {
158                $osc .= "<$action>\n".$oscpart->{$action}."</$action>\n";
159            }
160        }
161        $osc .= "</osmChange>\n";
162        my $res = OsmApi::post("changeset/$changeset/upload", $osc);
163        if (!($res->is_success))
164        {
165            print STDERR "changeset upload failed: ".$res->status_line."\n";
166            return undef;
167        }
168    }
169   
170    return 1;
171}
172
1731;
Note: See TracBrowser for help on using the repository browser.