sub cmd_dcommit()

in git-svn.perl [833:1094]


sub cmd_dcommit {
	my $head = shift;
	command_noisy(qw/update-index --refresh/);
	git_cmd_try { command_oneline(qw/diff-index --quiet HEAD --/) }
		'Cannot dcommit with a dirty index.  Commit your changes first, '
		. "or stash them with `git stash'.\n";
	$head ||= 'HEAD';

	my $old_head;
	if ($head ne 'HEAD') {
		$old_head = eval {
			command_oneline([qw/symbolic-ref -q HEAD/])
		};
		if ($old_head) {
			$old_head =~ s{^refs/heads/}{};
		} else {
			$old_head = eval { command_oneline(qw/rev-parse HEAD/) };
		}
		command(['checkout', $head], STDERR => 0);
	}

	my @refs;
	my ($url, $rev, $uuid, $gs) = working_head_info('HEAD', \@refs);
	unless ($gs) {
		die "Unable to determine upstream SVN information from ",
		    "$head history.\nPerhaps the repository is empty.";
	}

	if (defined $_commit_url) {
		$url = $_commit_url;
	} else {
		$url = eval { command_oneline('config', '--get',
			      "svn-remote.$gs->{repo_id}.commiturl") };
		if (!$url) {
			$url = $gs->full_pushurl
		}
	}

	my $last_rev = $_revision if defined $_revision;
	if ($url) {
		print "Committing to $url ...\n";
	}
	my ($linear_refs, $parents) = linearize_history($gs, \@refs);
	if ($_no_rebase && scalar(@$linear_refs) > 1) {
		warn "Attempting to commit more than one change while ",
		     "--no-rebase is enabled.\n",
		     "If these changes depend on each other, re-running ",
		     "without --no-rebase may be required."
	}

	if (defined $_interactive){
		my $ask_default = "y";
		foreach my $d (@$linear_refs){
			my ($fh, $ctx) = command_output_pipe(qw(show --summary), "$d");
			while (<$fh>){
				print $_;
			}
			command_close_pipe($fh, $ctx);
			$_ = ask("Commit this patch to SVN? ([y]es (default)|[n]o|[q]uit|[a]ll): ",
			         valid_re => qr/^(?:yes|y|no|n|quit|q|all|a)/i,
			         default => $ask_default);
			die "Commit this patch reply required" unless defined $_;
			if (/^[nq]/i) {
				exit(0);
			} elsif (/^a/i) {
				last;
			}
		}
	}

	my $expect_url = $url;

	my $push_merge_info = eval {
		command_oneline(qw/config --get svn.pushmergeinfo/)
		};
	if (not defined($push_merge_info)
			or $push_merge_info eq "false"
			or $push_merge_info eq "no"
			or $push_merge_info eq "never") {
		$push_merge_info = 0;
	}

	unless (defined($_merge_info) || ! $push_merge_info) {
		# Preflight check of changes to ensure no issues with mergeinfo
		# This includes check for uncommitted-to-SVN parents
		# (other than the first parent, which we will handle),
		# information from different SVN repos, and paths
		# which are not underneath this repository root.
		my $rooturl = $gs->repos_root;
	        Git::SVN::remove_username($rooturl);
		foreach my $d (@$linear_refs) {
			my %parentshash;
			read_commit_parents(\%parentshash, $d);
			my @realparents = @{$parentshash{$d}};
			if ($#realparents > 0) {
				# Merge commit
				shift @realparents; # Remove/ignore first parent
				foreach my $parent (@realparents) {
					my ($branchurl, $svnrev, $paruuid) = cmt_metadata($parent);
					unless (defined $paruuid) {
						# A parent is missing SVN annotations...
						# abort the whole operation.
						fatal "$parent is merged into revision $d, "
							 ."but does not have git-svn metadata. "
							 ."Either dcommit the branch or use a "
							 ."local cherry-pick, FF merge, or rebase "
							 ."instead of an explicit merge commit.";
					}

					unless ($paruuid eq $uuid) {
						# Parent has SVN metadata from different repository
						fatal "merge parent $parent for change $d has "
							 ."git-svn uuid $paruuid, while current change "
							 ."has uuid $uuid!";
					}

					unless ($branchurl =~ /^\Q$rooturl\E(.*)/) {
						# This branch is very strange indeed.
						fatal "merge parent $parent for $d is on branch "
							 ."$branchurl, which is not under the "
							 ."git-svn root $rooturl!";
					}
				}
			}
		}
	}

	my $rewritten_parent;
	my $current_head = command_oneline(qw/rev-parse HEAD/);
	Git::SVN::remove_username($expect_url);
	if (defined($_merge_info)) {
		$_merge_info =~ tr{ }{\n};
	}
	while (1) {
		my $d = shift @$linear_refs or last;
		unless (defined $last_rev) {
			(undef, $last_rev, undef) = cmt_metadata("$d~1");
			unless (defined $last_rev) {
				fatal "Unable to extract revision information ",
				      "from commit $d~1";
			}
		}
		if ($_dry_run) {
			print "diff-tree $d~1 $d\n";
		} else {
			my $cmt_rev;

			unless (defined($_merge_info) || ! $push_merge_info) {
				$_merge_info = populate_merge_info($d, $gs,
				                             $uuid,
				                             $linear_refs,
				                             $rewritten_parent);
			}

			my %ed_opts = ( r => $last_rev,
			                log => get_commit_entry($d)->{log},
			                ra => Git::SVN::Ra->new($url),
			                config => SVN::Core::config_get_config(
			                        $Git::SVN::Ra::config_dir
			                ),
			                tree_a => "$d~1",
			                tree_b => $d,
			                editor_cb => sub {
			                       print "Committed r$_[0]\n";
			                       $cmt_rev = $_[0];
			                },
					mergeinfo => $_merge_info,
			                svn_path => '');

			my $err_handler = $SVN::Error::handler;
			$SVN::Error::handler = sub {
				my $err = shift;
				dcommit_rebase(1, $current_head, $gs->refname,
					$err);
			};

			if (!Git::SVN::Editor->new(\%ed_opts)->apply_diff) {
				print "No changes\n$d~1 == $d\n";
			} elsif ($parents->{$d} && @{$parents->{$d}}) {
				$gs->{inject_parents_dcommit}->{$cmt_rev} =
				                               $parents->{$d};
			}
			$_fetch_all ? $gs->fetch_all : $gs->fetch;
			$SVN::Error::handler = $err_handler;
			$last_rev = $cmt_rev;
			next if $_no_rebase;

			my @diff = dcommit_rebase(@$linear_refs == 0, $d,
						$gs->refname, undef);

			$rewritten_parent = command_oneline(qw/rev-parse/,
							$gs->refname);

			if (@diff) {
				$current_head = command_oneline(qw/rev-parse
								HEAD/);
				@refs = ();
				my ($url_, $rev_, $uuid_, $gs_) =
				              working_head_info('HEAD', \@refs);
				my ($linear_refs_, $parents_) =
				              linearize_history($gs_, \@refs);
				if (scalar(@$linear_refs) !=
				    scalar(@$linear_refs_)) {
					fatal "# of revisions changed ",
					  "\nbefore:\n",
					  join("\n", @$linear_refs),
					  "\n\nafter:\n",
					  join("\n", @$linear_refs_), "\n",
					  'If you are attempting to commit ',
					  "merges, try running:\n\t",
					  'git rebase --interactive',
					  '--rebase-merges ',
					  $gs->refname,
					  "\nBefore dcommitting";
				}
				if ($url_ ne $expect_url) {
					if ($url_ eq $gs->metadata_url) {
						print
						  "Accepting rewritten URL:",
						  " $url_\n";
					} else {
						fatal
						  "URL mismatch after rebase:",
						  " $url_ != $expect_url";
					}
				}
				if ($uuid_ ne $uuid) {
					fatal "uuid mismatch after rebase: ",
					      "$uuid_ != $uuid";
				}
				# remap parents
				my (%p, @l, $i);
				for ($i = 0; $i < scalar @$linear_refs; $i++) {
					my $new = $linear_refs_->[$i] or next;
					$p{$new} =
						$parents->{$linear_refs->[$i]};
					push @l, $new;
				}
				$parents = \%p;
				$linear_refs = \@l;
				undef $last_rev;
			}
		}
	}

	if ($old_head) {
		my $new_head = command_oneline(qw/rev-parse HEAD/);
		my $new_is_symbolic = eval {
			command_oneline(qw/symbolic-ref -q HEAD/);
		};
		if ($new_is_symbolic) {
			print "dcommitted the branch ", $head, "\n";
		} else {
			print "dcommitted on a detached HEAD because you gave ",
			      "a revision argument.\n",
			      "The rewritten commit is: ", $new_head, "\n";
		}
		command(['checkout', $old_head], STDERR => 0);
	}

	unlink $gs->{index};
}