Merge branch 'rt/maint-clone-single' into maint
[gitweb.git] / contrib / mw-to-git / git-remote-mediawiki
index 8e46e4e7c710897444bc19b85a77914f76d634d1..68555d426540d710b5220168c9fa8eee9f946219 100755 (executable)
@@ -9,20 +9,7 @@
 # License: GPL v2 or later
 
 # Gateway between Git and MediaWiki.
-#   https://github.com/Bibzball/Git-Mediawiki/wiki
-#
-# Known limitations:
-#
-# - Several strategies are provided to fetch modifications from the
-#   wiki, but no automatic heuristics is provided, the user has
-#   to understand and chose which strategy is appropriate for him.
-#
-# - Git renames could be turned into MediaWiki renames (see TODO
-#   below)
-#
-# - No way to import "one page, and all pages included in it"
-#
-# - Multiple remote MediaWikis have not been very well tested.
+# Documentation & bugtracker: https://github.com/moy/Git-Mediawiki/
 
 use strict;
 use MediaWiki::API;
@@ -171,32 +158,6 @@ while (<STDIN>) {
 
 ## credential API management (generic functions)
 
-sub credential_from_url {
-       my $url = shift;
-       my $parsed = URI->new($url);
-       my %credential;
-
-       if ($parsed->scheme) {
-               $credential{protocol} = $parsed->scheme;
-       }
-       if ($parsed->host) {
-               $credential{host} = $parsed->host;
-       }
-       if ($parsed->path) {
-               $credential{path} = $parsed->path;
-       }
-       if ($parsed->userinfo) {
-               if ($parsed->userinfo =~ /([^:]*):(.*)/) {
-                       $credential{username} = $1;
-                       $credential{password} = $2;
-               } else {
-                       $credential{username} = $parsed->userinfo;
-               }
-       }
-
-       return %credential;
-}
-
 sub credential_read {
        my %credential;
        my $reader = shift;
@@ -214,8 +175,10 @@ sub credential_read {
 sub credential_write {
        my $credential = shift;
        my $writer = shift;
+       # url overwrites other fields, so it must come first
+       print $writer "url=$credential->{url}\n" if exists $credential->{url};
        while (my ($key, $value) = each(%$credential) ) {
-               if ($value) {
+               if (length $value && $key ne 'url') {
                        print $writer "$key=$value\n";
                }
        }
@@ -254,7 +217,7 @@ sub mw_connect_maybe {
        $mediawiki = MediaWiki::API->new;
        $mediawiki->{config}->{api_url} = "$url/api.php";
        if ($wiki_login) {
-               my %credential = credential_from_url($url);
+               my %credential = (url => $url);
                $credential{username} = $wiki_login;
                $credential{password} = $wiki_passwd;
                credential_run("fill", \%credential);
@@ -371,6 +334,8 @@ sub get_mw_first_pages {
 sub get_mw_pages {
        mw_connect_maybe();
 
+       print STDERR "Listing pages on remote wiki...\n";
+
        my %pages; # hash on page titles to avoid duplicates
        my $user_defined;
        if (@tracked_pages) {
@@ -394,6 +359,7 @@ sub get_mw_pages {
                        get_all_mediafiles(\%pages);
                }
        }
+       print STDERR (scalar keys %pages) . " pages found.\n";
        return %pages;
 }
 
@@ -576,6 +542,8 @@ sub get_last_remote_revision {
 
        my $max_rev_num = 0;
 
+       print STDERR "Getting last revision id on tracked pages...\n";
+
        foreach my $page (@pages) {
                my $id = $page->{pageid};
 
@@ -905,6 +873,10 @@ sub mw_import_revids {
        my $last_timestamp = 0; # Placeholer in case $rev->timestamp is undefined
 
        foreach my $pagerevid (@$revision_ids) {
+               # Count page even if we skip it, since we display
+               # $n/$total and $total includes skipped pages.
+               $n++;
+
                # fetch the content of the pages
                my $query = {
                        action => 'query',
@@ -919,6 +891,11 @@ sub mw_import_revids {
                        die "Failed to retrieve modified page for revision $pagerevid";
                }
 
+               if (defined($result->{query}->{badrevids}->{$pagerevid})) {
+                       # The revision id does not exist on the remote wiki.
+                       next;
+               }
+
                if (!defined($result->{query}->{pages})) {
                        die "Invalid revision $pagerevid.";
                }
@@ -927,10 +904,6 @@ sub mw_import_revids {
                my $result_page = $result_pages[0];
                my $rev = $result_pages[0]->{revisions}->[0];
 
-               # Count page even if we skip it, since we display
-               # $n/$total and $total includes skipped pages.
-               $n++;
-
                my $page_title = $result_page->{title};
 
                if (!exists($pages->{$page_title})) {
@@ -958,8 +931,11 @@ sub mw_import_revids {
                # Differentiates classic pages and media files.
                my ($namespace, $filename) = $page_title =~ /^([^:]*):(.*)$/;
                my %mediafile;
-               if ($namespace && get_mw_namespace_id($namespace) == get_mw_namespace_id("File")) {
-                       %mediafile = get_mw_mediafile_for_page_revision($filename, $rev->{timestamp});
+               if ($namespace) {
+                       my $id = get_mw_namespace_id($namespace);
+                       if ($id && $id == get_mw_namespace_id("File")) {
+                               %mediafile = get_mw_mediafile_for_page_revision($filename, $rev->{timestamp});
+                       }
                }
                # If this is a revision of the media page for new version
                # of a file do one common commit for both file and media page.
@@ -1196,16 +1172,26 @@ sub mw_push_revision {
        if ($last_local_revid > 0) {
                my $parsed_sha1 = $remoteorigin_sha1;
                # Find a path from last MediaWiki commit to pushed commit
+               print STDERR "Computing path from local to remote ...\n";
+               my @local_ancestry = split(/\n/, run_git("rev-list --boundary --parents $local ^$parsed_sha1"));
+               my %local_ancestry;
+               foreach my $line (@local_ancestry) {
+                       if (my ($child, $parents) = $line =~ m/^-?([a-f0-9]+) ([a-f0-9 ]+)/) {
+                               foreach my $parent (split(' ', $parents)) {
+                                       $local_ancestry{$parent} = $child;
+                               }
+                       } elsif (!$line =~ m/^([a-f0-9]+)/) {
+                               die "Unexpected output from git rev-list: $line";
+                       }
+               }
                while ($parsed_sha1 ne $HEAD_sha1) {
-                       my @commit_info =  grep(/^$parsed_sha1/, split(/\n/, run_git("rev-list --children $local")));
-                       if (!@commit_info) {
+                       my $child = $local_ancestry{$parsed_sha1};
+                       if (!$child) {
+                               printf STDERR "Cannot find a path in history from remote commit to last commit\n";
                                return error_non_fast_forward($remote);
                        }
-                       my @commit_info_split = split(/ |\n/, $commit_info[0]);
-                       # $commit_info_split[1] is the sha1 of the commit to export
-                       # $commit_info_split[0] is the sha1 of its direct child
-                       push(@commit_pairs, \@commit_info_split);
-                       $parsed_sha1 = $commit_info_split[1];
+                       push(@commit_pairs, [$parsed_sha1, $child]);
+                       $parsed_sha1 = $child;
                }
        } else {
                # No remote mediawiki revision. Export the whole
@@ -1253,7 +1239,7 @@ sub mw_push_revision {
                        }
                }
                unless ($dumb_push) {
-                       run_git("notes --ref=$remotename/mediawiki add -m \"mediawiki_revision: $mw_revision\" $sha1_commit");
+                       run_git("notes --ref=$remotename/mediawiki add -f -m \"mediawiki_revision: $mw_revision\" $sha1_commit");
                        run_git("update-ref -m \"Git-MediaWiki push\" refs/mediawiki/$remotename/master $sha1_commit $sha1_child");
                }
        }
@@ -1299,7 +1285,11 @@ sub get_mw_namespace_id {
                chomp(@temp);
                foreach my $ns (@temp) {
                        my ($n, $id) = split(/:/, $ns);
-                       $namespace_id{$n} = $id;
+                       if ($id eq 'notANameSpace') {
+                               $namespace_id{$n} = {is_namespace => 0};
+                       } else {
+                               $namespace_id{$n} = {is_namespace => 1, id => $id};
+                       }
                        $cached_mw_namespace_id{$n} = 1;
                }
        }
@@ -1317,28 +1307,38 @@ sub get_mw_namespace_id {
 
                while (my ($id, $ns) = each(%{$result->{query}->{namespaces}})) {
                        if (defined($ns->{id}) && defined($ns->{canonical})) {
-                               $namespace_id{$ns->{canonical}} = $ns->{id};
+                               $namespace_id{$ns->{canonical}} = {is_namespace => 1, id => $ns->{id}};
                                if ($ns->{'*'}) {
                                        # alias (e.g. french Fichier: as alias for canonical File:)
-                                       $namespace_id{$ns->{'*'}} = $ns->{id};
+                                       $namespace_id{$ns->{'*'}} = {is_namespace => 1, id => $ns->{id}};
                                }
                        }
                }
        }
 
-       my $id = $namespace_id{$name};
+       my $ns = $namespace_id{$name};
+       my $id;
 
-       if (defined $id) {
-               # Store explicitely requested namespaces on disk
-               if (!exists $cached_mw_namespace_id{$name}) {
-                       run_git("config --add remote.". $remotename
-                               .".namespaceCache \"". $name .":". $id ."\"");
-                       $cached_mw_namespace_id{$name} = 1;
-               }
-               return $id;
-       } else {
-               die "No such namespace $name on MediaWiki.";
+       unless (defined $ns) {
+               print STDERR "No such namespace $name on MediaWiki.\n";
+               $ns = {is_namespace => 0};
+               $namespace_id{$name} = $ns;
+       }
+
+       if ($ns->{is_namespace}) {
+               $id = $ns->{id};
+       }
+
+       # Store "notANameSpace" as special value for inexisting namespaces
+       my $store_id = ($id || 'notANameSpace');
+
+       # Store explicitely requested namespaces on disk
+       if (!exists $cached_mw_namespace_id{$name}) {
+               run_git("config --add remote.". $remotename
+                       .".namespaceCache \"". $name .":". $store_id ."\"");
+               $cached_mw_namespace_id{$name} = 1;
        }
+       return $id;
 }
 
 sub get_mw_namespace_id_for_page {