Merge branch 'lt/apply' into next

* lt/apply:
  apply --whitespace fixes and enhancements.
  The war on trailing whitespace
  svnimport: Convert the svn:ignore property
  svnimport: Convert executable flag
  svnimport: Mention -r in usage summary
  Make git diff-generation use a simpler spawn-like interface
This commit is contained in:
Junio C Hamano 2006-02-26 21:55:08 -08:00
commit 0a26233859
4 changed files with 232 additions and 80 deletions

View File

@ -10,10 +10,10 @@ git-svnimport - Import a SVN repository into git
SYNOPSIS
--------
'git-svnimport' [ -o <branch-for-HEAD> ] [ -h ] [ -v ] [ -d | -D ]
[ -C <GIT_repository> ] [ -i ] [ -u ] [-l limit_rev]
[ -b branch_subdir ] [ -T trunk_subdir ] [ -t tag_subdir ]
[ -s start_chg ] [ -m ] [ -M regex ]
<SVN_repository_URL> [ <path> ]
[ -C <GIT_repository> ] [ -i ] [ -u ] [-l limit_rev]
[ -b branch_subdir ] [ -T trunk_subdir ] [ -t tag_subdir ]
[ -s start_chg ] [ -m ] [ -r ] [ -M regex ]
[ -I <ignorefile_name> ] <SVN_repository_URL> [ <path> ]
DESCRIPTION
@ -65,6 +65,12 @@ When importing incrementally, you might need to edit the .git/svn2git file.
Prepend 'rX: ' to commit messages, where X is the imported
subversion revision.
-I <ignorefile_name>::
Import the svn:ignore directory property to files with this
name in each directory. (The Subversion and GIT ignore
syntaxes are similar enough that using the Subversion patterns
directly with "-I .gitignore" will almost always just work.)
-m::
Attempt to detect merges based on the commit message. This option
will enable default regexes that try to capture the name source

80
apply.c
View File

@ -34,6 +34,15 @@ static int line_termination = '\n';
static const char apply_usage[] =
"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] <patch>...";
static enum whitespace_eol {
nowarn,
warn_on_whitespace,
error_on_whitespace,
strip_and_apply,
} new_whitespace = nowarn;
static int whitespace_error = 0;
static const char *patch_input_file = NULL;
/*
* For "diff-stat" like behaviour, we keep track of the biggest change
* we've seen, and the longest filename. That allows us to do simple
@ -815,6 +824,20 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s
oldlines--;
break;
case '+':
/*
* We know len is at least two, since we have a '+' and
* we checked that the last character was a '\n' above.
* That is, an addition of an empty line would check
* the '+' here. Sneaky...
*/
if ((new_whitespace != nowarn) &&
isspace(line[len-2])) {
fprintf(stderr, "Added whitespace\n");
fprintf(stderr, "%s:%d:%.*s\n",
patch_input_file,
linenr, len-2, line+1);
whitespace_error = 1;
}
added++;
newlines--;
break;
@ -1092,6 +1115,27 @@ struct buffer_desc {
unsigned long alloc;
};
static int apply_line(char *output, const char *patch, int plen)
{
/* plen is number of bytes to be copied from patch,
* starting at patch+1 (patch[0] is '+'). Typically
* patch[plen] is '\n'.
*/
int add_nl_to_tail = 0;
if ((new_whitespace == strip_and_apply) &&
1 < plen && isspace(patch[plen-1])) {
if (patch[plen] == '\n')
add_nl_to_tail = 1;
plen--;
while (0 < plen && isspace(patch[plen]))
plen--;
}
memcpy(output, patch + 1, plen);
if (add_nl_to_tail)
output[plen++] = '\n';
return plen;
}
static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag)
{
char *buf = desc->buffer;
@ -1127,10 +1171,9 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag)
break;
/* Fall-through for ' ' */
case '+':
if (*patch != '+' || !no_add) {
memcpy(new + newsize, patch + 1, plen);
newsize += plen;
}
if (*patch != '+' || !no_add)
newsize += apply_line(new + newsize, patch,
plen);
break;
case '@': case '\\':
/* Ignore it, we already handled it */
@ -1699,7 +1742,7 @@ static int use_patch(struct patch *p)
return 1;
}
static int apply_patch(int fd)
static int apply_patch(int fd, const char *filename)
{
int newfd;
unsigned long offset, size;
@ -1707,6 +1750,7 @@ static int apply_patch(int fd)
struct patch *list = NULL, **listp = &list;
int skipped_patch = 0;
patch_input_file = filename;
if (!buffer)
return -1;
offset = 0;
@ -1733,6 +1777,9 @@ static int apply_patch(int fd)
}
newfd = -1;
if (whitespace_error && (new_whitespace == error_on_whitespace))
apply = 0;
write_index = check_index && apply;
if (write_index)
newfd = hold_index_file_for_update(&cache_file, get_index_file());
@ -1779,7 +1826,7 @@ int main(int argc, char **argv)
int fd;
if (!strcmp(arg, "-")) {
apply_patch(0);
apply_patch(0, "<stdin>");
read_stdin = 0;
continue;
}
@ -1839,6 +1886,21 @@ int main(int argc, char **argv)
line_termination = 0;
continue;
}
if (!strncmp(arg, "--whitespace=", 13)) {
if (!strcmp(arg+13, "warn")) {
new_whitespace = warn_on_whitespace;
continue;
}
if (!strcmp(arg+13, "error")) {
new_whitespace = error_on_whitespace;
continue;
}
if (!strcmp(arg+13, "strip")) {
new_whitespace = strip_and_apply;
continue;
}
die("unrecognixed whitespace option '%s'", arg+13);
}
if (check_index && prefix_length < 0) {
prefix = setup_git_directory();
@ -1852,10 +1914,12 @@ int main(int argc, char **argv)
if (fd < 0)
usage(apply_usage);
read_stdin = 0;
apply_patch(fd);
apply_patch(fd, arg);
close(fd);
}
if (read_stdin)
apply_patch(0);
apply_patch(0, "<stdin>");
if (whitespace_error && new_whitespace == error_on_whitespace)
return 1;
return 0;
}

138
diff.c
View File

@ -178,11 +178,12 @@ static void emit_rewrite_diff(const char *name_a,
copy_file('+', temp[1].name);
}
static void builtin_diff(const char *name_a,
static const char *builtin_diff(const char *name_a,
const char *name_b,
struct diff_tempfile *temp,
const char *xfrm_msg,
int complete_rewrite)
int complete_rewrite,
const char **args)
{
int i, next_at, cmd_size;
const char *const diff_cmd = "diff -L%s -L%s";
@ -242,19 +243,24 @@ static void builtin_diff(const char *name_a,
}
if (xfrm_msg && xfrm_msg[0])
puts(xfrm_msg);
/*
* we do not run diff between different kind
* of objects.
*/
if (strncmp(temp[0].mode, temp[1].mode, 3))
/* we do not run diff between different kind
* of objects.
*/
exit(0);
return NULL;
if (complete_rewrite) {
fflush(NULL);
emit_rewrite_diff(name_a, name_b, temp);
exit(0);
return NULL;
}
}
fflush(NULL);
execlp("/bin/sh","sh", "-c", cmd, NULL);
/* This is disgusting */
*args++ = "sh";
*args++ = "-c";
*args++ = cmd;
*args = NULL;
return "/bin/sh";
}
struct diff_filespec *alloc_filespec(const char *path)
@ -559,6 +565,40 @@ static void remove_tempfile_on_signal(int signo)
raise(signo);
}
static int spawn_prog(const char *pgm, const char **arg)
{
pid_t pid;
int status;
fflush(NULL);
pid = fork();
if (pid < 0)
die("unable to fork");
if (!pid) {
execvp(pgm, (char *const*) arg);
exit(255);
}
while (waitpid(pid, &status, 0) < 0) {
if (errno == EINTR)
continue;
return -1;
}
/* Earlier we did not check the exit status because
* diff exits non-zero if files are different, and
* we are not interested in knowing that. It was a
* mistake which made it harder to quit a diff-*
* session that uses the git-apply-patch-script as
* the GIT_EXTERNAL_DIFF. A custom GIT_EXTERNAL_DIFF
* should also exit non-zero only when it wants to
* abort the entire diff-* session.
*/
if (WIFEXITED(status) && !WEXITSTATUS(status))
return 0;
return -1;
}
/* An external diff command takes:
*
* diff-cmd name infile1 infile1-sha1 infile1-mode \
@ -573,9 +613,9 @@ static void run_external_diff(const char *pgm,
const char *xfrm_msg,
int complete_rewrite)
{
const char *spawn_arg[10];
struct diff_tempfile *temp = diff_temp;
pid_t pid;
int status;
int retval;
static int atexit_asked = 0;
const char *othername;
@ -592,59 +632,41 @@ static void run_external_diff(const char *pgm,
signal(SIGINT, remove_tempfile_on_signal);
}
fflush(NULL);
pid = fork();
if (pid < 0)
die("unable to fork");
if (!pid) {
if (pgm) {
if (one && two) {
const char *exec_arg[10];
const char **arg = &exec_arg[0];
*arg++ = pgm;
*arg++ = name;
*arg++ = temp[0].name;
*arg++ = temp[0].hex;
*arg++ = temp[0].mode;
*arg++ = temp[1].name;
*arg++ = temp[1].hex;
*arg++ = temp[1].mode;
if (other) {
*arg++ = other;
*arg++ = xfrm_msg;
}
*arg = NULL;
execvp(pgm, (char *const*) exec_arg);
if (pgm) {
const char **arg = &spawn_arg[0];
if (one && two) {
*arg++ = pgm;
*arg++ = name;
*arg++ = temp[0].name;
*arg++ = temp[0].hex;
*arg++ = temp[0].mode;
*arg++ = temp[1].name;
*arg++ = temp[1].hex;
*arg++ = temp[1].mode;
if (other) {
*arg++ = other;
*arg++ = xfrm_msg;
}
else
execlp(pgm, pgm, name, NULL);
} else {
*arg++ = pgm;
*arg++ = name;
}
/*
* otherwise we use the built-in one.
*/
if (one && two)
builtin_diff(name, othername, temp, xfrm_msg,
complete_rewrite);
else
*arg = NULL;
} else {
if (one && two) {
pgm = builtin_diff(name, othername, temp, xfrm_msg, complete_rewrite, spawn_arg);
} else
printf("* Unmerged path %s\n", name);
exit(0);
}
if (waitpid(pid, &status, 0) < 0 ||
!WIFEXITED(status) || WEXITSTATUS(status)) {
/* Earlier we did not check the exit status because
* diff exits non-zero if files are different, and
* we are not interested in knowing that. It was a
* mistake which made it harder to quit a diff-*
* session that uses the git-apply-patch-script as
* the GIT_EXTERNAL_DIFF. A custom GIT_EXTERNAL_DIFF
* should also exit non-zero only when it wants to
* abort the entire diff-* session.
*/
remove_tempfile();
retval = 0;
if (pgm)
retval = spawn_prog(pgm, spawn_arg);
remove_tempfile();
if (retval) {
fprintf(stderr, "external diff died, stopping at %s.\n", name);
exit(1);
}
remove_tempfile();
}
static void diff_fill_sha1_info(struct diff_filespec *one)

View File

@ -29,19 +29,21 @@ die "Need SVN:Core 1.2.1 or better" if $SVN::Core::VERSION lt "1.2.1";
$SIG{'PIPE'}="IGNORE";
$ENV{'TZ'}="UTC";
our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T,$opt_b,$opt_r,$opt_s,$opt_l,$opt_d,$opt_D);
our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T,
$opt_b,$opt_r,$opt_I,$opt_s,$opt_l,$opt_d,$opt_D);
sub usage() {
print STDERR <<END;
Usage: ${\basename $0} # fetch/update GIT from SVN
[-o branch-for-HEAD] [-h] [-v] [-l max_rev]
[-C GIT_repository] [-t tagname] [-T trunkname] [-b branchname]
[-d|-D] [-i] [-u] [-r] [-s start_chg] [-m] [-M regex] [SVN_URL]
[-d|-D] [-i] [-u] [-r] [-I ignorefilename] [-s start_chg]
[-m] [-M regex] [SVN_URL]
END
exit(1);
}
getopts("b:C:dDhil:mM:o:rs:t:T:uv") or usage();
getopts("b:C:dDhiI:l:mM:o:rs:t:T:uv") or usage();
usage if $opt_h;
my $tag_name = $opt_t || "tags";
@ -112,16 +114,40 @@ sub file {
DIR => File::Spec->tmpdir(), UNLINK => 1);
print "... $rev $path ...\n" if $opt_v;
my $pool = SVN::Pool->new();
eval { $self->{'svn'}->get_file($path,$rev,$fh,$pool); };
$pool->clear;
my (undef, $properties);
eval { (undef, $properties)
= $self->{'svn'}->get_file($path,$rev,$fh); };
if($@) {
return undef if $@ =~ /Attempted to get checksum/;
die $@;
}
my $mode;
if (exists $properties->{'svn:executable'}) {
$mode = '0755';
} else {
$mode = '0644';
}
close ($fh);
return $name;
return ($name, $mode);
}
sub ignore {
my($self,$path,$rev) = @_;
print "... $rev $path ...\n" if $opt_v;
my (undef,undef,$properties)
= $self->{'svn'}->get_dir($path,$rev,undef);
if (exists $properties->{'svn:ignore'}) {
my ($fh, $name) = tempfile('gitsvn.XXXXXX',
DIR => File::Spec->tmpdir(),
UNLINK => 1);
print $fh $properties->{'svn:ignore'};
close($fh);
return $name;
} else {
return undef;
}
}
package main;
@ -296,7 +322,7 @@ sub get_file($$$) {
my $svnpath = revert_split_path($branch,$path);
# now get it
my $name;
my ($name,$mode);
if($opt_d) {
my($req,$res);
@ -316,8 +342,9 @@ sub get_file($$$) {
return undef if $res->code == 301; # directory?
die $res->status_line." at $url\n";
}
$mode = '0644'; # can't obtain mode via direct http request?
} else {
$name = $svn->file("$svnpath",$rev);
($name,$mode) = $svn->file("$svnpath",$rev);
return undef unless defined $name;
}
@ -331,10 +358,37 @@ sub get_file($$$) {
chomp $sha;
close $F;
unlink $name;
my $mode = "0644"; # SV does not seem to store any file modes
return [$mode, $sha, $path];
}
sub get_ignore($$$$$) {
my($new,$old,$rev,$branch,$path) = @_;
return unless $opt_I;
my $svnpath = revert_split_path($branch,$path);
my $name = $svn->ignore("$svnpath",$rev);
if ($path eq '/') {
$path = $opt_I;
} else {
$path = File::Spec->catfile($path,$opt_I);
}
if (defined $name) {
my $pid = open(my $F, '-|');
die $! unless defined $pid;
if (!$pid) {
exec("git-hash-object", "-w", $name)
or die "Cannot create object: $!\n";
}
my $sha = <$F>;
chomp $sha;
close $F;
unlink $name;
push(@$new,['0644',$sha,$path]);
} else {
push(@$old,$path);
}
}
sub split_path($$) {
my($rev,$path) = @_;
my $branch;
@ -540,6 +594,9 @@ sub commit {
my $opath = $action->[3];
print STDERR "$revision: $branch: could not fetch '$opath'\n";
}
} elsif ($node_kind eq $SVN::Node::dir) {
get_ignore(\@new, \@old, $revision,
$branch,$path);
}
} elsif ($action->[0] eq "D") {
push(@old,$path);
@ -548,6 +605,9 @@ sub commit {
if ($node_kind eq $SVN::Node::file) {
my $f = get_file($revision,$branch,$path);
push(@new,$f) if $f;
} elsif ($node_kind eq $SVN::Node::dir) {
get_ignore(\@new, \@old, $revision,
$branch,$path);
}
} else {
die "$revision: unknown action '".$action->[0]."' for $path\n";