git-branch -D: make it work even when on a yet-to-be-born branch
[gitweb.git] / git-svn.perl
index 37ecc517879aa2c18cc909c60d3bf6ceed82fb48..47cd3e27fe2892bbdd7c91efcd349040ef57e9b8 100755 (executable)
@@ -39,7 +39,7 @@
 memoize('cmt_metadata');
 memoize('get_commit_time');
 
-my ($SVN_PATH, $SVN, $SVN_LOG, $_use_lib);
+my ($SVN_PATH, $SVN, $SVN_LOG, $_use_lib, $AUTH_BATON, $AUTH_CALLBACKS);
 
 sub nag_lib {
        print STDERR <<EOF;
@@ -66,7 +66,8 @@ sub nag_lib {
        $_template, $_shared, $_no_default_regex, $_no_graft_copy,
        $_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit,
        $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m,
-       $_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive);
+       $_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive,
+       $_username, $_config_dir, $_no_auth_cache);
 my (@_branch_from, %tree_map, %users, %rusers, %equiv);
 my ($_svn_co_url_revs, $_svn_pg_peg_revs);
 my @repo_path_split_cache;
@@ -79,6 +80,9 @@ sub nag_lib {
                'repack:i' => \$_repack,
                'no-metadata' => \$_no_metadata,
                'quiet|q' => \$_q,
+               'username=s' => \$_username,
+               'config-dir=s' => \$_config_dir,
+               'no-auth-cache' => \$_no_auth_cache,
                'ignore-nodate' => \$_ignore_nodate,
                'repack-flags|repack-args|repack-opts=s' => \$_repack_flags);
 
@@ -134,6 +138,7 @@ sub nag_lib {
        'commit-diff' => [ \&commit_diff, 'Commit a diff between two trees',
                        { 'message|m=s' => \$_message,
                          'file|F=s' => \$_file,
+                         'revision|r=s' => \$_revision,
                        %cmt_opts } ],
        dcommit => [ \&dcommit, 'Commit several diffs to merge with upstream',
                        { 'merge|m|M' => \$_merge,
@@ -231,7 +236,7 @@ sub rebuild {
                my @commit = grep(/^git-svn-id: /,`git-cat-file commit $c`);
                next if (!@commit); # skip merges
                my ($url, $rev, $uuid) = extract_metadata($commit[$#commit]);
-               if (!$rev || !$uuid) {
+               if (!defined $rev || !$uuid) {
                        croak "Unable to extract revision or UUID from ",
                                "$c, $commit[$#commit]\n";
                }
@@ -586,11 +591,28 @@ sub commit_lib {
 sub dcommit {
        my $gs = "refs/remotes/$GIT_SVN";
        chomp(my @refs = safe_qx(qw/git-rev-list --no-merges/, "$gs..HEAD"));
+       my $last_rev;
        foreach my $d (reverse @refs) {
+               if (quiet_run('git-rev-parse','--verify',"$d~1") != 0) {
+                       die "Commit $d\n",
+                           "has no parent commit, and therefore ",
+                           "nothing to diff against.\n",
+                           "You should be working from a repository ",
+                           "originally created by git-svn\n";
+               }
+               unless (defined $last_rev) {
+                       (undef, $last_rev, undef) = cmt_metadata("$d~1");
+                       unless (defined $last_rev) {
+                               die "Unable to extract revision information ",
+                                   "from commit $d~1\n";
+                       }
+               }
                if ($_dry_run) {
                        print "diff-tree $d~1 $d\n";
                } else {
-                       commit_diff("$d~1", $d);
+                       if (my $r = commit_diff("$d~1", $d, undef, $last_rev)) {
+                               $last_rev = $r;
+                       } # else: no changes, same $last_rev
                }
        }
        return if $_dry_run;
@@ -605,7 +627,7 @@ sub dcommit {
        } else {
                print "No changes between current HEAD and $gs\n",
                      "Hard resetting to the latest $gs\n";
-               @finish = qw/reset --hard/;
+               @finish = qw/reset --mixed/;
        }
        sys('git', @finish, $gs);
 }
@@ -814,6 +836,14 @@ sub commit_diff {
                print STDERR "Needed URL or usable git-svn id command-line\n";
                commit_diff_usage();
        }
+       my $r = shift;
+       unless (defined $r) {
+               if (defined $_revision) {
+                       $r = $_revision
+               } else {
+                       die "-r|--revision is a required argument\n";
+               }
+       }
        if (defined $_message && defined $_file) {
                print STDERR "Both --message/-m and --file/-F specified ",
                                "for the commit message.\n",
@@ -830,13 +860,22 @@ sub commit_diff {
        ($repo, $SVN_PATH) = repo_path_split($SVN_URL);
        $SVN_LOG ||= libsvn_connect($repo);
        $SVN ||= libsvn_connect($repo);
+       if ($r eq 'HEAD') {
+               $r = $SVN->get_latest_revnum;
+       } elsif ($r !~ /^\d+$/) {
+               die "revision argument: $r not understood by git-svn\n";
+       }
        my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
-       my $ed = SVN::Git::Editor->new({        r => $SVN->get_latest_revnum,
+       my $rev_committed;
+       my $ed = SVN::Git::Editor->new({        r => $r,
                                                ra => $SVN_LOG, c => $tb,
                                                svn_path => $SVN_PATH
                                        },
                                $SVN->get_commit_editor($_message,
-                                       sub {print "Committed $_[0]\n"},@lock)
+                                       sub {
+                                               $rev_committed = $_[0];
+                                               print "Committed $_[0]\n";
+                                       }, @lock)
                                );
        my $mods = libsvn_checkout_tree($ta, $tb, $ed);
        if (@$mods == 0) {
@@ -846,6 +885,7 @@ sub commit_diff {
                $ed->close_edit;
        }
        $_message = $_file = undef;
+       return $rev_committed;
 }
 
 ########################### utility functions #########################
@@ -2463,7 +2503,7 @@ sub extract_metadata {
        my $id = shift or return (undef, undef, undef);
        my ($url, $rev, $uuid) = ($id =~ /^git-svn-id:\s(\S+?)\@(\d+)
                                                        \s([a-f\d\-]+)$/x);
-       if (!$rev || !$uuid || !$url) {
+       if (!defined $rev || !$uuid || !$url) {
                # some of the original repositories I made had
                # identifiers like this:
                ($rev, $uuid) = ($id =~/^git-svn-id:\s(\d+)\@([a-f\d\-]+)/);
@@ -2647,26 +2687,163 @@ sub libsvn_load {
                my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file.
                                        $SVN::Node::dir.$SVN::Node::unknown.
                                        $SVN::Node::none.$SVN::Node::file.
-                                       $SVN::Node::dir.$SVN::Node::unknown;
+                                       $SVN::Node::dir.$SVN::Node::unknown.
+                                       $SVN::Auth::SSL::CNMISMATCH.
+                                       $SVN::Auth::SSL::NOTYETVALID.
+                                       $SVN::Auth::SSL::EXPIRED.
+                                       $SVN::Auth::SSL::UNKNOWNCA.
+                                       $SVN::Auth::SSL::OTHER;
                1;
        };
 }
 
+sub _simple_prompt {
+       my ($cred, $realm, $default_username, $may_save, $pool) = @_;
+       $may_save = undef if $_no_auth_cache;
+       $default_username = $_username if defined $_username;
+       if (defined $default_username && length $default_username) {
+               if (defined $realm && length $realm) {
+                       print "Authentication realm: $realm\n";
+               }
+               $cred->username($default_username);
+       } else {
+               _username_prompt($cred, $realm, $may_save, $pool);
+       }
+       $cred->password(_read_password("Password for '" .
+                                      $cred->username . "': ", $realm));
+       $cred->may_save($may_save);
+       $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub _ssl_server_trust_prompt {
+       my ($cred, $realm, $failures, $cert_info, $may_save, $pool) = @_;
+       $may_save = undef if $_no_auth_cache;
+       print "Error validating server certificate for '$realm':\n";
+       if ($failures & $SVN::Auth::SSL::UNKNOWNCA) {
+               print " - The certificate is not issued by a trusted ",
+                     "authority. Use the\n",
+                     "   fingerprint to validate the certificate manually!\n";
+       }
+       if ($failures & $SVN::Auth::SSL::CNMISMATCH) {
+               print " - The certificate hostname does not match.\n";
+       }
+       if ($failures & $SVN::Auth::SSL::NOTYETVALID) {
+               print " - The certificate is not yet valid.\n";
+       }
+       if ($failures & $SVN::Auth::SSL::EXPIRED) {
+               print " - The certificate has expired.\n";
+       }
+       if ($failures & $SVN::Auth::SSL::OTHER) {
+               print " - The certificate has an unknown error.\n";
+       }
+       printf( "Certificate information:\n".
+               " - Hostname: %s\n".
+               " - Valid: from %s until %s\n".
+               " - Issuer: %s\n".
+               " - Fingerprint: %s\n",
+               map $cert_info->$_, qw(hostname valid_from valid_until
+                                      issuer_dname fingerprint) );
+       my $choice;
+prompt:
+       print $may_save ?
+             "(R)eject, accept (t)emporarily or accept (p)ermanently? " :
+             "(R)eject or accept (t)emporarily? ";
+       $choice = lc(substr(<STDIN> || 'R', 0, 1));
+       if ($choice =~ /^t$/i) {
+               $cred->may_save(undef);
+       } elsif ($choice =~ /^r$/i) {
+               return -1;
+       } elsif ($may_save && $choice =~ /^p$/i) {
+               $cred->may_save($may_save);
+       } else {
+               goto prompt;
+       }
+       $cred->accepted_failures($failures);
+       $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub _ssl_client_cert_prompt {
+       my ($cred, $realm, $may_save, $pool) = @_;
+       $may_save = undef if $_no_auth_cache;
+       print "Client certificate filename: ";
+       chomp(my $filename = <STDIN>);
+       $cred->cert_file($filename);
+       $cred->may_save($may_save);
+       $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub _ssl_client_cert_pw_prompt {
+       my ($cred, $realm, $may_save, $pool) = @_;
+       $may_save = undef if $_no_auth_cache;
+       $cred->password(_read_password("Password: ", $realm));
+       $cred->may_save($may_save);
+       $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub _username_prompt {
+       my ($cred, $realm, $may_save, $pool) = @_;
+       $may_save = undef if $_no_auth_cache;
+       if (defined $realm && length $realm) {
+               print "Authentication realm: $realm\n";
+       }
+       my $username;
+       if (defined $_username) {
+               $username = $_username;
+       } else {
+               print "Username: ";
+               chomp($username = <STDIN>);
+       }
+       $cred->username($username);
+       $cred->may_save($may_save);
+       $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub _read_password {
+       my ($prompt, $realm) = @_;
+       print $prompt;
+       require Term::ReadKey;
+       Term::ReadKey::ReadMode('noecho');
+       my $password = '';
+       while (defined(my $key = Term::ReadKey::ReadKey(0))) {
+               last if $key =~ /[\012\015]/; # \n\r
+               $password .= $key;
+       }
+       Term::ReadKey::ReadMode('restore');
+       print "\n";
+       $password;
+}
+
 sub libsvn_connect {
        my ($url) = @_;
-       my $auth = SVN::Core::auth_open([SVN::Client::get_simple_provider(),
-                         SVN::Client::get_ssl_server_trust_file_provider(),
-                         SVN::Client::get_username_provider()]);
-       my $s = eval { SVN::Ra->new(url => $url, auth => $auth) };
-       return $s;
+       if (!$AUTH_BATON || !$AUTH_CALLBACKS) {
+               SVN::_Core::svn_config_ensure($_config_dir, undef);
+               ($AUTH_BATON, $AUTH_CALLBACKS) = SVN::Core::auth_open_helper([
+                   SVN::Client::get_simple_provider(),
+                   SVN::Client::get_ssl_server_trust_file_provider(),
+                   SVN::Client::get_simple_prompt_provider(
+                     \&_simple_prompt, 2),
+                   SVN::Client::get_ssl_client_cert_prompt_provider(
+                     \&_ssl_client_cert_prompt, 2),
+                   SVN::Client::get_ssl_client_cert_pw_prompt_provider(
+                     \&_ssl_client_cert_pw_prompt, 2),
+                   SVN::Client::get_username_provider(),
+                   SVN::Client::get_ssl_server_trust_prompt_provider(
+                     \&_ssl_server_trust_prompt),
+                   SVN::Client::get_username_prompt_provider(
+                     \&_username_prompt, 2),
+                 ]);
+       }
+       SVN::Ra->new(url => $url, auth => $AUTH_BATON,
+                    auth_provider_callbacks => $AUTH_CALLBACKS);
 }
 
 sub libsvn_get_file {
-       my ($gui, $f, $rev) = @_;
+       my ($gui, $f, $rev, $chg) = @_;
        my $p = $f;
        if (length $SVN_PATH > 0) {
                return unless ($p =~ s#^\Q$SVN_PATH\E/##);
        }
+       print "\t$chg\t$f\n" unless $_q;
 
        my ($hash, $pid, $in, $out);
        my $pool = SVN::Pool->new;
@@ -2769,8 +2946,7 @@ sub libsvn_fetch {
                $pool->clear;
        }
        foreach (@amr) {
-               print "\t$_->[0]\t$_->[1]\n" unless $_q;
-               libsvn_get_file($gui, $_->[1], $rev)
+               libsvn_get_file($gui, $_->[1], $rev, $_->[0]);
        }
        close $gui or croak $?;
        return libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]);
@@ -2848,8 +3024,7 @@ sub libsvn_traverse {
                        if (defined $files) {
                                push @$files, $file;
                        } else {
-                               print "\tA\t$file\n" unless $_q;
-                               libsvn_get_file($gui, $file, $rev);
+                               libsvn_get_file($gui, $file, $rev, 'A');
                        }
                }
        }
@@ -3140,7 +3315,7 @@ sub copy_remote_ref {
        my $ref = "refs/remotes/$GIT_SVN";
        if (safe_qx('git-ls-remote', $origin, $ref)) {
                sys(qw/git fetch/, $origin, "$ref:$ref");
-       } else {
+       } elsif ($_cp_remote && !$_upgrade) {
                die "Unable to find remote reference: ",
                                "refs/remotes/$GIT_SVN on $origin\n";
        }