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
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
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
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;
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;
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 */
return 1;
}
-static int apply_patch(int fd)
+static int apply_patch(int fd, const char *filename)
{
int newfd;
unsigned long offset, size;
struct patch *list = NULL, **listp = &list;
int skipped_patch = 0;
+ patch_input_file = filename;
if (!buffer)
return -1;
offset = 0;
}
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());
int fd;
if (!strcmp(arg, "-")) {
- apply_patch(0);
+ apply_patch(0, "<stdin>");
read_stdin = 0;
continue;
}
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();
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;
}
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";
}
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)
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 \
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;
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)
$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";
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;
my $svnpath = revert_split_path($branch,$path);
# now get it
- my $name;
+ my ($name,$mode);
if($opt_d) {
my($req,$res);
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;
}
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;
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);
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";