Merge branch 'jc/remote' into next

* jc/remote:
  git-remote
  Blame "linenr" link jumps to previous state at "orig_lineno"
  gitweb: Fix "Use of uninitialized value" warning in git_tags_body
  git-svn: make --repack work consistently between fetch and multi-fetch
  git-svn: update documentation for multi-{init|fetch}
  git-svn: make multi-init less confusing
  Remove shadowing variable from traverse_trees()
  Make check target depend on common-cmds.h
  rerere: Fix removal of already resolved path.
  pack-check.c::verify_packfile(): don't run SHA-1 update on huge data
  Fix infinite loop when deleting multiple packed refs.

Conflicts:

	pack-check.c
This commit is contained in:
Junio C Hamano
2007-01-04 23:23:17 -08:00
10 changed files with 384 additions and 71 deletions

View File

@@ -3,7 +3,7 @@ git-svn(1)
NAME
----
git-svn - bidirectional operation between a single Subversion branch and git
git-svn - bidirectional operation between Subversion and git
SYNOPSIS
--------
@@ -11,24 +11,20 @@ SYNOPSIS
DESCRIPTION
-----------
git-svn is a simple conduit for changesets between a single Subversion
branch and git. It is not to be confused with gitlink:git-svnimport[1].
They were designed with very different goals in mind.
git-svn is a simple conduit for changesets between Subversion and git.
It is not to be confused with gitlink:git-svnimport[1], which is
read-only and geared towards tracking multiple branches.
git-svn is designed for an individual developer who wants a
git-svn was originally designed for an individual developer who wants a
bidirectional flow of changesets between a single branch in Subversion
and an arbitrary number of branches in git. git-svnimport is designed
for read-only operation on repositories that match a particular layout
(albeit the recommended one by SVN developers).
and an arbitrary number of branches in git. Since its inception,
git-svn has gained the ability to track multiple branches in a manner
similar to git-svnimport; but it cannot (yet) automatically detect new
branches and tags like git-svnimport does.
For importing svn, git-svnimport is potentially more powerful when
operating on repositories organized under the recommended
trunk/branch/tags structure, and should be faster, too.
git-svn mostly ignores the very limited view of branching that
Subversion has. This allows git-svn to be much easier to use,
especially on repositories that are not organized in a manner that
git-svnimport is designed for.
git-svn is especially useful when it comes to tracking repositories
not organized in the way Subversion developers recommend (trunk,
branches, tags directories).
COMMANDS
--------
@@ -370,7 +366,7 @@ SVN was very wrong.
Basic Examples
~~~~~~~~~~~~~~
Tracking and contributing to a Subversion-managed project:
Tracking and contributing to a the trunk of a Subversion-managed project:
------------------------------------------------------------------------
# Initialize a repo (like git init-db):
@@ -388,6 +384,30 @@ Tracking and contributing to a Subversion-managed project:
git-svn show-ignore >> .git/info/exclude
------------------------------------------------------------------------
Tracking and contributing to an entire Subversion-managed project
(complete with a trunk, tags and branches):
See also:
'<<tracking-multiple-repos,Tracking Multiple Repositories or Branches>>'
------------------------------------------------------------------------
# Initialize a repo (like git init-db):
git-svn multi-init http://svn.foo.org/project \
-T trunk -b branches -t tags
# Fetch remote revisions:
git-svn multi-fetch
# Create your own branch of trunk to hack on:
git checkout -b my-trunk remotes/trunk
# Do some work, and then commit your new changes to SVN, as well as
# automatically updating your working HEAD:
git-svn dcommit -i trunk
# Something has been committed to trunk, rebase the latest into your branch:
git-svn multi-fetch && git rebase remotes/trunk
# Append svn:ignore settings of trunk to the default git exclude file:
git-svn show-ignore -i trunk >> .git/info/exclude
# Check for new branches and tags (no arguments are needed):
git-svn multi-init
------------------------------------------------------------------------
REBASE VS. PULL
---------------

View File

@@ -179,7 +179,7 @@ SCRIPT_SH = \
SCRIPT_PERL = \
git-add--interactive.perl \
git-archimport.perl git-cvsimport.perl git-relink.perl \
git-cvsserver.perl \
git-cvsserver.perl git-remote.perl \
git-svnimport.perl git-cvsexportcommit.perl \
git-send-email.perl git-svn.perl
@@ -818,7 +818,7 @@ test-sha1$X: test-sha1.o $(GITLIBS)
check-sha1:: test-sha1$X
./test-sha1.sh
check:
check: common-cmds.h
for i in *.c; do sparse $(ALL_CFLAGS) $(SPARSE_FLAGS) $$i || exit; done

View File

@@ -350,11 +350,10 @@ static int do_plain_rerere(struct path_list *rr, int fd)
fprintf(stderr, "Recorded resolution for '%s'.\n", path);
copy_file(path, rr_path(name, "postimage"));
tail_optimization:
if (i < rr->nr - 1) {
if (i < rr->nr - 1)
memmove(rr->items + i,
rr->items + i + 1,
rr->nr - i - 1);
}
rr->items + i + 1,
sizeof(rr->items[0]) * (rr->nr - i - 1));
rr->nr--;
i--;
}

View File

@@ -179,6 +179,7 @@ extern int refresh_cache(unsigned int flags);
struct lock_file {
struct lock_file *next;
char on_list;
char filename[PATH_MAX];
};
extern int hold_lock_file_for_update(struct lock_file *, const char *path, int);

277
git-remote.perl Executable file
View File

@@ -0,0 +1,277 @@
#!/usr/bin/perl -w
use Git;
my $git = Git->repository();
sub add_remote_config {
my ($hash, $name, $what, $value) = @_;
if ($what eq 'url') {
if (exists $hash->{$name}{'URL'}) {
print STDERR "Warning: more than one remote.$name.url\n";
}
$hash->{$name}{'URL'} = $value;
}
elsif ($what eq 'fetch') {
$hash->{$name}{'FETCH'} ||= [];
push @{$hash->{$name}{'FETCH'}}, $value;
}
if (!exists $hash->{$name}{'SOURCE'}) {
$hash->{$name}{'SOURCE'} = 'config';
}
}
sub add_remote_remotes {
my ($hash, $file, $name) = @_;
if (exists $hash->{$name}) {
$hash->{$name}{'WARNING'} = 'ignored due to config';
return;
}
my $fh;
if (!open($fh, '<', $file)) {
print STDERR "Warning: cannot open $file\n";
return;
}
my $it = { 'SOURCE' => 'remotes' };
$hash->{$name} = $it;
while (<$fh>) {
chomp;
if (/^URL:\s*(.*)$/) {
# Having more than one is Ok -- it is used for push.
if (! exists $it->{'URL'}) {
$it->{'URL'} = $1;
}
}
elsif (/^Push:\s*(.*)$/) {
; # later
}
elsif (/^Pull:\s*(.*)$/) {
$it->{'FETCH'} ||= [];
push @{$it->{'FETCH'}}, $1;
}
elsif (/^\#/) {
; # ignore
}
else {
print STDERR "Warning: funny line in $file: $_\n";
}
}
close($fh);
}
sub list_remote {
my ($git) = @_;
my %seen = ();
my @remotes = eval {
$git->command(qw(repo-config --get-regexp), '^remote\.');
};
for (@remotes) {
if (/^remote\.([^.]*)\.(\S*)\s+(.*)$/) {
add_remote_config(\%seen, $1, $2, $3);
}
}
my $dir = $git->repo_path() . "/remotes";
if (opendir(my $dh, $dir)) {
local $_;
while ($_ = readdir($dh)) {
chomp;
next if (! -f "$dir/$_" || ! -r _);
add_remote_remotes(\%seen, "$dir/$_", $_);
}
}
return \%seen;
}
sub add_branch_config {
my ($hash, $name, $what, $value) = @_;
if ($what eq 'remote') {
if (exists $hash->{$name}{'REMOTE'}) {
print STDERR "Warning: more than one branch.$name.remote\n";
}
$hash->{$name}{'REMOTE'} = $value;
}
elsif ($what eq 'merge') {
$hash->{$name}{'MERGE'} ||= [];
push @{$hash->{$name}{'MERGE'}}, $value;
}
}
sub list_branch {
my ($git) = @_;
my %seen = ();
my @branches = eval {
$git->command(qw(repo-config --get-regexp), '^branch\.');
};
for (@branches) {
if (/^branch\.([^.]*)\.(\S*)\s+(.*)$/) {
add_branch_config(\%seen, $1, $2, $3);
}
}
return \%seen;
}
my $remote = list_remote($git);
my $branch = list_branch($git);
sub update_ls_remote {
my ($harder, $info) = @_;
return if (($harder == 0) ||
(($harder == 1) && exists $info->{'LS_REMOTE'}));
my @ref = map {
s|^[0-9a-f]{40}\s+refs/heads/||;
$_;
} $git->command(qw(ls-remote --heads), $info->{'URL'});
$info->{'LS_REMOTE'} = \@ref;
}
sub show_wildcard_mapping {
my ($forced, $ours, $ls) = @_;
my %refs;
for (@$ls) {
$refs{$_} = 01; # bit #0 to say "they have"
}
for ($git->command('for-each-ref', "refs/remotes/$ours")) {
chomp;
next unless (s|^[0-9a-f]{40}\s[a-z]+\srefs/remotes/$ours/||);
next if ($_ eq 'HEAD');
$refs{$_} ||= 0;
$refs{$_} |= 02; # bit #1 to say "we have"
}
my (@new, @stale, @tracked);
for (sort keys %refs) {
my $have = $refs{$_};
if ($have == 1) {
push @new, $_;
}
elsif ($have == 2) {
push @stale, $_;
}
elsif ($have == 3) {
push @tracked, $_;
}
}
if (@new) {
print " New remote branches (next fetch will store in remotes/$ours)\n";
print " @new\n";
}
if (@stale) {
print " Stale tracking branches in remotes/$ours (you'd better remove them)\n";
print " @stale\n";
}
if (@tracked) {
print " Tracked remote branches\n";
print " @tracked\n";
}
}
sub show_mapping {
my ($name, $info) = @_;
my $fetch = $info->{'FETCH'};
my $ls = $info->{'LS_REMOTE'};
my (@stale, @tracked);
for (@$fetch) {
next unless (/(\+)?([^:]+):(.*)/);
my ($forced, $theirs, $ours) = ($1, $2, $3);
if ($theirs eq 'refs/heads/*' &&
$ours =~ /^refs\/remotes\/(.*)\/\*$/) {
# wildcard mapping
show_wildcard_mapping($forced, $1, $ls);
}
elsif ($theirs =~ /\*/ || $ours =~ /\*/) {
print STDERR "Warning: unrecognized mapping in remotes.$name.fetch: $_\n";
}
elsif ($theirs =~ s|^refs/heads/||) {
if (!grep { $_ eq $theirs } @$ls) {
push @stale, $theirs;
}
elsif ($ours ne '') {
push @tracked, $theirs;
}
}
}
if (@stale) {
print " Stale tracking branches in remotes/$name (you'd better remove them)\n";
print " @stale\n";
}
if (@tracked) {
print " Tracked remote branches\n";
print " @tracked\n";
}
}
sub show_remote {
my ($name, $ls_remote) = @_;
if (!exists $remote->{$name}) {
print STDERR "No such remote $name\n";
return;
}
my $info = $remote->{$name};
update_ls_remote($ls_remote, $info);
print "* remote $name\n";
print " URL: $info->{'URL'}\n";
for my $branchname (sort keys %$branch) {
next if ($branch->{$branchname}{'REMOTE'} ne $name);
my @merged = map {
s|^refs/heads/||;
$_;
} split(' ',"@{$branch->{$branchname}{'MERGE'}}");
next unless (@merged);
print " Remote branch(es) merged with 'git pull' while on branch $branchname\n";
print " @merged\n";
}
if ($info->{'LS_REMOTE'}) {
show_mapping($name, $info);
}
}
sub add_remote {
my ($name, $url) = @_;
if (exists $remote->{$name}) {
print STDERR "remote $name already exists.\n";
exit(1);
}
$git->command('repo-config', "remote.$name.url", $url);
$git->command('repo-config', "remote.$name.fetch",
"+refs/heads/*:refs/remotes/$name/*");
}
if (!@ARGV) {
for (sort keys %$remote) {
print "$_\n";
}
}
elsif ($ARGV[0] eq 'show') {
my $ls_remote = 1;
my $i;
for ($i = 1; $i < @ARGV; $i++) {
if ($ARGV[$i] eq '-n') {
$ls_remote = 0;
}
else {
last;
}
}
if ($i >= @ARGV) {
print STDERR "Usage: git remote show <remote>\n";
exit(1);
}
for (; $i < @ARGV; $i++) {
show_remote($ARGV[$i], $ls_remote);
}
}
elsif ($ARGV[0] eq 'add') {
if (@ARGV != 3) {
print STDERR "Usage: git remote add <name> <url>\n";
exit(1);
}
add_remote($ARGV[1], $ARGV[2]);
}

View File

@@ -102,7 +102,7 @@ my %cmt_opts = ( 'edit|e' => \$_edit,
);
my %cmd = (
fetch => [ \&fetch, "Download new revisions from SVN",
fetch => [ \&cmd_fetch, "Download new revisions from SVN",
{ 'revision|r=s' => \$_revision, %fc_opts } ],
init => [ \&init, "Initialize a repo for tracking" .
" (requires URL argument)",
@@ -293,6 +293,10 @@ sub init {
setup_git_svn();
}
sub cmd_fetch {
fetch_child_id($GIT_SVN, @_);
}
sub fetch {
check_upgrade_needed();
$SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url");
@@ -571,28 +575,25 @@ sub graft_branches {
sub multi_init {
my $url = shift;
$_trunk ||= 'trunk';
$_trunk =~ s#/+$##;
$url =~ s#/+$## if $url;
if ($_trunk !~ m#^[a-z\+]+://#) {
$_trunk = '/' . $_trunk if ($_trunk !~ m#^/#);
unless ($url) {
print STDERR "E: '$_trunk' is not a complete URL ",
"and a separate URL is not specified\n";
exit 1;
unless (defined $_trunk || defined $_branches || defined $_tags) {
usage(1);
}
if (defined $_trunk) {
my $trunk_url = complete_svn_url($url, $_trunk);
my $ch_id;
if ($GIT_SVN eq 'git-svn') {
$ch_id = 1;
$GIT_SVN = $ENV{GIT_SVN_ID} = 'trunk';
}
init_vars();
unless (-d $GIT_SVN_DIR) {
if ($ch_id) {
print "GIT_SVN_ID set to 'trunk' for ",
"$trunk_url ($_trunk)\n";
}
init($trunk_url);
command_noisy('repo-config', 'svn.trunk', $trunk_url);
}
$_trunk = $url . $_trunk;
}
my $ch_id;
if ($GIT_SVN eq 'git-svn') {
$ch_id = 1;
$GIT_SVN = $ENV{GIT_SVN_ID} = 'trunk';
}
init_vars();
unless (-d $GIT_SVN_DIR) {
print "GIT_SVN_ID set to 'trunk' for $_trunk\n" if $ch_id;
init($_trunk);
command_noisy('repo-config', 'svn.trunk', $_trunk);
}
complete_url_ls_init($url, $_branches, '--branches/-b', '');
complete_url_ls_init($url, $_tags, '--tags/-t', 'tags/');
@@ -839,7 +840,6 @@ sub fetch_child_id {
my $ref = "$GIT_DIR/refs/remotes/$id";
defined(my $pid = open my $fh, '-|') or croak $!;
if (!$pid) {
$_repack = undef;
$GIT_SVN = $ENV{GIT_SVN_ID} = $id;
init_vars();
fetch(@_);
@@ -847,7 +847,7 @@ sub fetch_child_id {
}
while (<$fh>) {
print $_;
check_repack() if (/^r\d+ = $sha1/);
check_repack() if (/^r\d+ = $sha1/o);
}
close $fh or croak $?;
}
@@ -872,29 +872,34 @@ sub rec_fetch {
}
}
sub complete_svn_url {
my ($url, $path) = @_;
$path =~ s#/+$##;
$url =~ s#/+$## if $url;
if ($path !~ m#^[a-z\+]+://#) {
$path = '/' . $path if ($path !~ m#^/#);
if (!defined $url || $url !~ m#^[a-z\+]+://#) {
fatal("E: '$path' is not a complete URL ",
"and a separate URL is not specified\n");
}
$path = $url . $path;
}
return $path;
}
sub complete_url_ls_init {
my ($url, $var, $switch, $pfx) = @_;
unless ($var) {
my ($url, $path, $switch, $pfx) = @_;
unless ($path) {
print STDERR "W: $switch not specified\n";
return;
}
$var =~ s#/+$##;
if ($var !~ m#^[a-z\+]+://#) {
$var = '/' . $var if ($var !~ m#^/#);
unless ($url) {
print STDERR "E: '$var' is not a complete URL ",
"and a separate URL is not specified\n";
exit 1;
}
$var = $url . $var;
}
my @ls = libsvn_ls_fullurl($var);
my $old = $GIT_SVN;
my $full_url = complete_svn_url($url, $path);
my @ls = libsvn_ls_fullurl($full_url);
defined(my $pid = fork) or croak $!;
if (!$pid) {
foreach my $u (map { "$var/$_" } (grep m!/$!, @ls)) {
foreach my $u (map { "$full_url/$_" } (grep m!/$!, @ls)) {
$u =~ s#/+$##;
if ($u !~ m!\Q$var\E/(.+)$!) {
if ($u !~ m!\Q$full_url\E/(.+)$!) {
print STDERR "W: Unrecognized URL: $u\n";
die "This should never happen\n";
}
@@ -912,7 +917,7 @@ sub complete_url_ls_init {
waitpid $pid, 0;
croak $? if $?;
my ($n) = ($switch =~ /^--(\w+)/);
command_noisy('repo-config', "svn.$n", $var);
command_noisy('repo-config', "svn.$n", $full_url);
}
sub common_prefix {
@@ -1405,7 +1410,6 @@ sub git_commit {
# this output is read via pipe, do not change:
print "r$log_msg->{revision} = $commit\n";
check_repack();
return $commit;
}

View File

@@ -2813,8 +2813,12 @@ sub git_tags_body {
print "<tr class=\"light\">\n";
}
$alternate ^= 1;
print "<td><i>$tag{'age'}</i></td>\n" .
"<td>" .
if (defined $tag{'age'}) {
print "<td><i>$tag{'age'}</i></td>\n";
} else {
print "<td></td>\n";
}
print "<td>" .
$cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'}),
-class => "list name"}, esc_html($tag{'name'})) .
"</td>\n" .
@@ -3208,9 +3212,14 @@ HTML
esc_html($rev));
print "</td>\n";
}
open (my $dd, "-|", git_cmd(), "rev-parse", "$full_rev^")
or die_error("could not open git-rev-parse");
my $parent_commit = <$dd>;
close $dd;
chomp($parent_commit);
my $blamed = href(action => 'blame',
file_name => $meta->{'filename'},
hash_base => $full_rev);
hash_base => $parent_commit);
print "<td class=\"linenr\">";
print $cgi->a({ -href => "$blamed#l$orig_lineno",
-id => "l$lineno",

View File

@@ -27,9 +27,12 @@ static int lock_file(struct lock_file *lk, const char *path)
sprintf(lk->filename, "%s.lock", path);
fd = open(lk->filename, O_RDWR | O_CREAT | O_EXCL, 0666);
if (0 <= fd) {
if (!lk->next) {
if (!lk->on_list) {
lk->next = lock_file_list;
lock_file_list = lk;
lk->on_list = 1;
}
if (lock_file_list) {
signal(SIGINT, remove_lock_file_on_signal);
atexit(remove_lock_file);
}
@@ -37,6 +40,8 @@ static int lock_file(struct lock_file *lk, const char *path)
return error("cannot fix permission bits on %s",
lk->filename);
}
else
lk->filename[0] = 0;
return fd;
}

1
refs.c
View File

@@ -726,7 +726,6 @@ static int repack_without_ref(const char *refname)
}
if (!found)
return 0;
memset(&packlock, 0, sizeof(packlock));
fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0);
if (fd < 0)
return error("cannot delete '%s' from packed refs", refname);

View File

@@ -113,7 +113,6 @@ void traverse_trees(int n, struct tree_desc *t, const char *base, traverse_callb
struct name_entry *entry = xmalloc(n*sizeof(*entry));
for (;;) {
struct name_entry entry[3];
unsigned long mask = 0;
int i, last;