All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] git-svn: teach to create and operate on bare repositories
@ 2009-04-19 17:57 Eygene Ryabinkin
  2009-04-21 18:01 ` Eric Wong
  0 siblings, 1 reply; 4+ messages in thread
From: Eygene Ryabinkin @ 2009-04-19 17:57 UTC (permalink / raw)
  To: Eric Wong; +Cc: git

This mode is useful when we're mirrorring one-to-one Subversion and Git
repositories and then using Git as the repository to base other work on.

Bare mode is currently restricted only to init, clone, fetch, log,
find-rev, show-ignore, show-externals, create-ignore, propget, proplist
and info commands.  Others won't work at all and error message will
be produced.

Signed-off-by: Eygene Ryabinkin <rea-git@codelabs.ru>
---
 Documentation/git-svn.txt |   23 ++++++++
 git-svn.perl              |  130 +++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 149 insertions(+), 4 deletions(-)

diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt
index 9229d45..4749676 100644
--- a/Documentation/git-svn.txt
+++ b/Documentation/git-svn.txt
@@ -104,6 +104,10 @@ COMMANDS
 --parent;;
 	Fetch only from the SVN parent of the current HEAD.
 
+--bare;;
+	Creates bare repository without checked-out copy.  See section
+	BARE REPOSITORIES for details.
+
 This doesn't interfere with interoperating with the Subversion
 repository you cloned from, but if you wish for your local Git
 repository to be able to interoperate with someone else's local Git
@@ -537,6 +541,25 @@ and these settings should never be changed once they are set.
 Additionally, only one of these four options can be used per-svn-remote
 section because they affect the 'git-svn-id:' metadata line.
 
+BARE REPOSITORIES
+-----------------
+
+Bare repositories are most suitable for serving them via 'git-daemon',
+so they can be used when one wants to convert Subversion repository to
+Git and base the work on the created Git repository.  This was the
+initial idea of adding support for such repositories: repository is
+thought to be read-only for Git clients, but could be updated via 'git
+svn fetch' to catch changes in Subversion repository.
+
+Only subset of all 'git svn' commands are available for bare
+repositories: init, clone, fetch, log, find-rev, show-ignore,
+show-externals, create-ignore, propget, proplist and info.
+
+One certainly could push to bare repository that is maintained by
+'git-svn', but merges are not supported on the bare repositories, so it
+could be hard to recover from such push.  The bottom line: currently
+supported mode is read-only for all Git clients and only this mode is
+now tested and supported.  Git wizards are, of course, free for all.
 
 BASIC EXAMPLES
 --------------
diff --git a/git-svn.perl b/git-svn.perl
index c5965c9..4756abf 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -63,13 +63,14 @@ $sha1 = qr/[a-f\d]{40}/;
 $sha1_short = qr/[a-f\d]{4,40}/;
 my ($_stdin, $_help, $_edit,
 	$_message, $_file,
-	$_template, $_shared,
+	$_template, $_shared, $_bare,
 	$_version, $_fetch_all, $_no_rebase, $_fetch_parent,
 	$_merge, $_strategy, $_dry_run, $_local,
 	$_prefix, $_no_checkout, $_url, $_verbose,
 	$_git_format, $_commit_url, $_tag);
 $Git::SVN::_follow_parent = 1;
 $_q ||= 0;
+$_bare = 0;
 my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username,
                     'config-dir=s' => \$Git::SVN::Ra::config_dir,
                     'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache,
@@ -96,6 +97,7 @@ my %init_opts = ( 'template=s' => \$_template, 'shared:s' => \$_shared,
                   'trunk|T=s' => \$_trunk, 'tags|t=s' => \$_tags,
                   'branches|b=s' => \$_branches, 'prefix=s' => \$_prefix,
                   'stdlayout|s' => \$_stdlayout,
+                  'bare' => \$_bare,
                   'minimize-url|m' => \$Git::SVN::_minimize_url,
 		  'no-metadata' => sub { $icv{noMetadata} = 1 },
 		  'use-svm-props' => sub { $icv{useSvmProps} = 1 },
@@ -315,7 +317,7 @@ sub version {
 }
 
 sub do_git_init_db {
-	unless (-d $ENV{GIT_DIR}) {
+	unless (-d $ENV{GIT_DIR} && !$_bare) {
 		my @init_db = ('init');
 		push @init_db, "--template=$_template" if defined $_template;
 		if (defined $_shared) {
@@ -325,8 +327,15 @@ sub do_git_init_db {
 				push @init_db, "--shared";
 			}
 		}
+		push @init_db, "--bare" if $_bare;
 		command_noisy(@init_db);
-		$_repository = Git->repository(Repository => ".git");
+		if ($_bare && -d $ENV{GIT_DIR}) {
+			$_repository =
+			    Git->repository(Repository => $ENV{GIT_DIR});
+		} else {
+			$_repository =
+			    Git->repository(Repository => ".git");
+		}
 	}
 	command_noisy('config', 'core.autocrlf', 'false');
 	my $set;
@@ -344,9 +353,20 @@ sub do_git_init_db {
 
 sub init_subdir {
 	my $repo_path = shift or return;
+	my $full_path;
+	if ($repo_path =~ /^\//) {
+		$full_path = $repo_path;
+	} else {
+		use POSIX qw(getcwd);
+		$full_path = POSIX::getcwd() . "/" . $repo_path;
+	}
 	mkpath([$repo_path]) unless -d $repo_path;
 	chdir $repo_path or die "Couldn't chdir to $repo_path: $!\n";
-	$ENV{GIT_DIR} = '.git';
+	if (!$_bare) {
+		$ENV{GIT_DIR} = $full_path . '/.git';
+	} else {
+		$ENV{GIT_DIR} = $full_path;
+	}
 	$_repository = Git->repository(Repository => $ENV{GIT_DIR});
 }
 
@@ -389,6 +409,7 @@ sub cmd_fetch {
 	if (@_ > 1) {
 		die "Usage: $0 fetch [--all] [--parent] [svn-remote]\n";
 	}
+	get_bareness();
 	if ($_fetch_parent) {
 		my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
 		unless ($gs) {
@@ -408,6 +429,12 @@ sub cmd_fetch {
 
 sub cmd_set_tree {
 	my (@commits) = @_;
+
+	get_bareness();
+	if ($_bare) {
+		fatal("'set-tree' isn't supported for bare repositories.");
+	}
+
 	if ($_stdin || !@commits) {
 		print "Reading from stdin...\n";
 		@commits = ();
@@ -444,6 +471,12 @@ sub cmd_set_tree {
 
 sub cmd_dcommit {
 	my $head = shift;
+
+	get_bareness();
+	if ($_bare) {
+		fatal("'dcommit' isn't supported for bare repositories.");
+	}
+
 	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";
@@ -579,6 +612,11 @@ sub cmd_dcommit {
 sub cmd_branch {
 	my ($branch_name, $head) = @_;
 
+	get_bareness();
+	if ($_bare) {
+		fatal("'branch' isn't supported for bare repositories.");
+	}
+
 	unless (defined $branch_name && length $branch_name) {
 		die(($_tag ? "tag" : "branch") . " name required\n");
 	}
@@ -635,6 +673,11 @@ sub cmd_find_rev {
 }
 
 sub cmd_rebase {
+	get_bareness();
+	if ($_bare) {
+		fatal("'rebase' isn't supported for bare repositories.");
+	}
+
 	command_noisy(qw/update-index --refresh/);
 	my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
 	unless ($gs) {
@@ -793,6 +836,11 @@ sub cmd_proplist {
 }
 
 sub cmd_multi_init {
+	get_bareness();
+	if ($_bare) {
+		fatal("'multi-init' isn't supported for bare repositories.");
+	}
+
 	my $url = shift;
 	unless (defined $_trunk || defined $_branches || defined $_tags) {
 		usage(1);
@@ -836,6 +884,11 @@ sub cmd_multi_fetch {
 
 # this command is special because it requires no metadata
 sub cmd_commit_diff {
+	get_bareness();
+	if ($_bare) {
+		fatal("'commit-diff' isn't supported for bare repositories.");
+	}
+
 	my ($ta, $tb, $url) = @_;
 	my $usage = "Usage: $0 commit-diff -r<revision> ".
 	            "<tree-ish> <tree-ish> [<URL>]";
@@ -1252,6 +1305,14 @@ sub read_repo_config {
 	delete @$opts{@config_only} if @config_only;
 }
 
+# Initialize bareness flag for an existing repository
+sub get_bareness {
+	return unless -d $ENV{GIT_DIR};
+	my $result = Git::config_bool('core.bare');
+	$result = 0 unless defined($result);
+	$_bare = $result;
+}
+
 sub extract_metadata {
 	my $id = shift or return (undef, undef, undef);
 	my ($url, $rev, $uuid) = ($id =~ /^\s*git-svn-id:\s+(.*)\@(\d+)
@@ -1527,6 +1588,51 @@ sub parse_revision_argument {
 	die "revision argument: $::_revision not understood by git-svn\n";
 }
 
+#
+# While we are fetching to bare repositories, we should update branch
+# heads manually, because it is not possible to do merges within bare
+# repositories.
+#
+# Arguments:
+# - name of remote branch, for example 'refs/remotes/git-svn';
+# - name of the corresponding local head, for example 'refs/heads/master'.
+#
+sub fast_forward_bare_fetch {
+	return unless defined($_bare);
+	return unless defined($_[0]);
+	return unless defined($_[1]);
+	my $remote = $_[0];
+	my $localhead = $_[1];
+
+	# Gather current SHA1 codes for both objects.
+	my $new_sha1ref = eval {
+		command_oneline(qw/show-ref --hash/, $remote)
+	};
+	return unless length($new_sha1ref);
+	my $current_sha1ref = eval {
+		command_oneline(qw/show-ref --hash/, $localhead)
+	};
+	return unless length($current_sha1ref);
+
+	# Same?  Nothing to do.
+	return if $new_sha1ref eq $current_sha1ref;
+
+	# Update SHA1 for local head and verify new value.
+	command_noisy('update-ref', $localhead, $new_sha1ref);
+	$current_sha1ref = eval {
+		command_oneline(qw/show-ref --hash/, $localhead)
+	};
+	if ($current_sha1ref eq $new_sha1ref) {
+		unless ($::_q > 1) {
+			printf "Fast-forwarded %s to %s.\n",
+		  	  $localhead, $new_sha1ref;
+		}
+	} else {
+		printf "Error fast forwarding %s to %s.\n",
+		  $localhead, $new_sha1ref;
+	}
+}
+
 sub fetch_all {
 	my ($repo_id, $remotes) = @_;
 	if (ref $repo_id) {
@@ -1571,6 +1677,22 @@ sub fetch_all {
 
 	($base, $head) = parse_revision_argument($base, $head);
 	$ra->gs_fetch_loop_common($base, $head, \@gs, \@globs);
+
+	# Update (fast-forward) heads for bare repository.
+	if (defined($_bare)) {
+		foreach my $gs (@gs) {
+			my $remote = 'refs/remotes/' . $gs->{ref_id};
+			my $localhead = 'refs/heads/master';
+			if (open(FH, "<", "HEAD")) {
+				chomp(my $head = <FH>);
+				close FH;
+				if ($head =~ /^ref: (.*)$/) {
+					$localhead = $1;
+				}
+			}
+			fast_forward_bare_fetch($remote, $localhead);
+		}
+	}
 }
 
 sub read_all_remotes {
-- 
1.6.1.3

^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH] git-svn: teach to create and operate on bare repositories
  2009-04-19 17:57 [PATCH] git-svn: teach to create and operate on bare repositories Eygene Ryabinkin
@ 2009-04-21 18:01 ` Eric Wong
  2009-04-22 21:36   ` Eygene Ryabinkin
  0 siblings, 1 reply; 4+ messages in thread
From: Eric Wong @ 2009-04-21 18:01 UTC (permalink / raw)
  To: Eygene Ryabinkin; +Cc: git

Eygene Ryabinkin <rea-git@codelabs.ru> wrote:
> This mode is useful when we're mirrorring one-to-one Subversion and Git
> repositories and then using Git as the repository to base other work on.
> 
> Bare mode is currently restricted only to init, clone, fetch, log,
> find-rev, show-ignore, show-externals, create-ignore, propget, proplist
> and info commands.  Others won't work at all and error message will
> be produced.

Hi Eygene,

This definitely seems useful.  I'd like a basic test to ensure it
continues working in the future.

More comments inline.

> Signed-off-by: Eygene Ryabinkin <rea-git@codelabs.ru>

> index c5965c9..4756abf 100755
> --- a/git-svn.perl
> +++ b/git-svn.perl
> @@ -63,13 +63,14 @@ $sha1 = qr/[a-f\d]{40}/;
>  $sha1_short = qr/[a-f\d]{4,40}/;
>  my ($_stdin, $_help, $_edit,
>  	$_message, $_file,
> -	$_template, $_shared,
> +	$_template, $_shared, $_bare,
>  	$_version, $_fetch_all, $_no_rebase, $_fetch_parent,
>  	$_merge, $_strategy, $_dry_run, $_local,
>  	$_prefix, $_no_checkout, $_url, $_verbose,
>  	$_git_format, $_commit_url, $_tag);
>  $Git::SVN::_follow_parent = 1;
>  $_q ||= 0;
> +$_bare = 0;
>  my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username,
>                      'config-dir=s' => \$Git::SVN::Ra::config_dir,
>                      'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache,

>  }
>  
>  sub do_git_init_db {
> -	unless (-d $ENV{GIT_DIR}) {
> +	unless (-d $ENV{GIT_DIR} && !$_bare) {
>  		my @init_db = ('init');
>  		push @init_db, "--template=$_template" if defined $_template;
>  		if (defined $_shared) {
> @@ -325,8 +327,15 @@ sub do_git_init_db {
>  				push @init_db, "--shared";
>  			}
>  		}
> +		push @init_db, "--bare" if $_bare;
>  		command_noisy(@init_db);
> -		$_repository = Git->repository(Repository => ".git");
> +		if ($_bare && -d $ENV{GIT_DIR}) {
> +			$_repository =
> +			    Git->repository(Repository => $ENV{GIT_DIR});
> +		} else {
> +			$_repository =
> +			    Git->repository(Repository => ".git");
> +		}
>  	}
>  	command_noisy('config', 'core.autocrlf', 'false');
>  	my $set;
> @@ -344,9 +353,20 @@ sub do_git_init_db {
>  
>  sub init_subdir {
>  	my $repo_path = shift or return;
> +	my $full_path;
> +	if ($repo_path =~ /^\//) {
> +		$full_path = $repo_path;
> +	} else {
> +		use POSIX qw(getcwd);
> +		$full_path = POSIX::getcwd() . "/" . $repo_path;
> +	}
>  	mkpath([$repo_path]) unless -d $repo_path;
>  	chdir $repo_path or die "Couldn't chdir to $repo_path: $!\n";
> -	$ENV{GIT_DIR} = '.git';
> +	if (!$_bare) {
> +		$ENV{GIT_DIR} = $full_path . '/.git';
> +	} else {
> +		$ENV{GIT_DIR} = $full_path;
> +	}
>  	$_repository = Git->repository(Repository => $ENV{GIT_DIR});
>  }
>  
> @@ -389,6 +409,7 @@ sub cmd_fetch {
>  	if (@_ > 1) {
>  		die "Usage: $0 fetch [--all] [--parent] [svn-remote]\n";
>  	}
> +	get_bareness();
>  	if ($_fetch_parent) {
>  		my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
>  		unless ($gs) {
> @@ -408,6 +429,12 @@ sub cmd_fetch {
>  
>  sub cmd_set_tree {
>  	my (@commits) = @_;
> +
> +	get_bareness();
> +	if ($_bare) {
> +		fatal("'set-tree' isn't supported for bare repositories.");
> +	}
> +

This repetition with get_bareness() and then checking for $_bare all
over the place bothers me a bit.  I'd rather just have something like
this:

	fatal_if_bare('set-tree');

Or even:

	fatal_if_bare($cmd);

> +# Initialize bareness flag for an existing repository
> +sub get_bareness {
> +	return unless -d $ENV{GIT_DIR};
> +	my $result = Git::config_bool('core.bare');
> +	$result = 0 unless defined($result);
> +	$_bare = $result;
> +}

Perhaps this function can go into Git.pm since core.bare affects all
of git, not just git-svn.  I'd also like to just use something like

	if (Git::config_bare()) { ... }

...rather than having to check/initialize a variable every time.

Of course, we can have config caching for platforms that really need
it done via Sam's Git::Config module when it gets merged.

>  sub extract_metadata {
>  	my $id = shift or return (undef, undef, undef);
>  	my ($url, $rev, $uuid) = ($id =~ /^\s*git-svn-id:\s+(.*)\@(\d+)
> @@ -1527,6 +1588,51 @@ sub parse_revision_argument {
>  	die "revision argument: $::_revision not understood by git-svn\n";
>  }
>  
> +#
> +# While we are fetching to bare repositories, we should update branch
> +# heads manually, because it is not possible to do merges within bare
> +# repositories.
> +#
> +# Arguments:
> +# - name of remote branch, for example 'refs/remotes/git-svn';
> +# - name of the corresponding local head, for example 'refs/heads/master'.
> +#
> +sub fast_forward_bare_fetch {
> +	return unless defined($_bare);
> +	return unless defined($_[0]);
> +	return unless defined($_[1]);

Checking for arguments here seems unnecessarily defensive.

> +	my $remote = $_[0];
> +	my $localhead = $_[1];

The general git-svn style is this:

	my ($remote, $localhead) = @_;

> +	# Update (fast-forward) heads for bare repository.
> +	if (defined($_bare)) {
> +		foreach my $gs (@gs) {
> +			my $remote = 'refs/remotes/' . $gs->{ref_id};
> +			my $localhead = 'refs/heads/master';
> +			if (open(FH, "<", "HEAD")) {
> +				chomp(my $head = <FH>);
> +				close FH;
> +				if ($head =~ /^ref: (.*)$/) {
> +					$localhead = $1;
> +				}
> +			}

"git rev-parse --symbolic-full-name HEAD" should work better
than parsing HEAD ourselves.

Thanks!

-- 
Eric Wong

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH] git-svn: teach to create and operate on bare repositories
  2009-04-21 18:01 ` Eric Wong
@ 2009-04-22 21:36   ` Eygene Ryabinkin
  2009-04-23 18:21     ` Eric Wong
  0 siblings, 1 reply; 4+ messages in thread
From: Eygene Ryabinkin @ 2009-04-22 21:36 UTC (permalink / raw)
  To: Eric Wong; +Cc: git

[-- Attachment #1: Type: text/plain, Size: 3442 bytes --]

Eric, good day.

Tue, Apr 21, 2009 at 11:01:01AM -0700, Eric Wong wrote:
> This definitely seems useful.  I'd like a basic test to ensure it
> continues working in the future.

No problems, answers inline ;))

> >  sub cmd_set_tree {
> >  	my (@commits) = @_;
> > +
> > +	get_bareness();
> > +	if ($_bare) {
> > +		fatal("'set-tree' isn't supported for bare repositories.");
> > +	}
> > +
> 
> This repetition with get_bareness() and then checking for $_bare all
> over the place bothers me a bit.  I'd rather just have something like
> this:
> 
> 	fatal_if_bare('set-tree');
> 
> Or even:
> 
> 	fatal_if_bare($cmd);

Done.

> > +# Initialize bareness flag for an existing repository
> > +sub get_bareness {
> > +	return unless -d $ENV{GIT_DIR};
> > +	my $result = Git::config_bool('core.bare');
> > +	$result = 0 unless defined($result);
> > +	$_bare = $result;
> > +}
> 
> Perhaps this function can go into Git.pm since core.bare affects all
> of git, not just git-svn.  I'd also like to just use something like
> 
> 	if (Git::config_bare()) { ... }
> 
> ...rather than having to check/initialize a variable every time.
> 
> Of course, we can have config caching for platforms that really need
> it done via Sam's Git::Config module when it gets merged.

Hmm, for my needs the bare "Git::config_bool('core.bare')" is sufficient,
so I implanted it everywhere.

> >  sub extract_metadata {
> >  	my $id = shift or return (undef, undef, undef);
> >  	my ($url, $rev, $uuid) = ($id =~ /^\s*git-svn-id:\s+(.*)\@(\d+)
> > @@ -1527,6 +1588,51 @@ sub parse_revision_argument {
> >  	die "revision argument: $::_revision not understood by git-svn\n";
> >  }
> >  
> > +#
> > +# While we are fetching to bare repositories, we should update branch
> > +# heads manually, because it is not possible to do merges within bare
> > +# repositories.
> > +#
> > +# Arguments:
> > +# - name of remote branch, for example 'refs/remotes/git-svn';
> > +# - name of the corresponding local head, for example 'refs/heads/master'.
> > +#
> > +sub fast_forward_bare_fetch {
> > +	return unless defined($_bare);
> > +	return unless defined($_[0]);
> > +	return unless defined($_[1]);
> 
> Checking for arguments here seems unnecessarily defensive.

I used to this style, but OK, it was eliminated.

> 
> > +	my $remote = $_[0];
> > +	my $localhead = $_[1];
> 
> The general git-svn style is this:
> 
> 	my ($remote, $localhead) = @_;

Fixed.

> > +	# Update (fast-forward) heads for bare repository.
> > +	if (defined($_bare)) {
> > +		foreach my $gs (@gs) {
> > +			my $remote = 'refs/remotes/' . $gs->{ref_id};
> > +			my $localhead = 'refs/heads/master';
> > +			if (open(FH, "<", "HEAD")) {
> > +				chomp(my $head = <FH>);
> > +				close FH;
> > +				if ($head =~ /^ref: (.*)$/) {
> > +					$localhead = $1;
> > +				}
> > +			}
> 
> "git rev-parse --symbolic-full-name HEAD" should work better
> than parsing HEAD ourselves.

Used it.  The resulting patch is attached.

I have one question: as you can see from the above hunk, I am using HEAD
as the tip of all local branches to be fast-forwarded.  It works with
only one upstream branch, but what if there will be multiple
(disconnected) remotes we are tracking?  I had seen the words that it it
possible (in the git-svn man page), but hadn't managed to create such
repository and figure out how can I deduce the name of the local head
for the remote one.  Could you, please, enlighten me?

Thanks!
-- 
Eygene

[-- Attachment #2: git-svn-teach-to-create-and-operate-on-bare-repos.diff --]
[-- Type: text/x-diff, Size: 9117 bytes --]

From cc509782cfa3c17dc830015891cde2a11275a2c4 Mon Sep 17 00:00:00 2001
From: Eygene Ryabinkin <rea@codelabs.ru>
Date: Thu, 16 Apr 2009 02:27:24 +0400
Subject: [PATCH] git-svn: teach to create and operate on bare repositories

This mode is useful when we're mirrorring one-to-one Subversion and Git
repositories and then using Git as the repository to base other work on.

Bare mode is currently restricted only to init, clone, fetch, log,
find-rev, show-ignore, show-externals, create-ignore, propget, proplist
and info commands.  Others won't work at all and error message will
be produced.

Signed-off-by: Eygene Ryabinkin <rea-git@codelabs.ru>
---
 Documentation/git-svn.txt |   23 +++++++++
 git-svn.perl              |  110 +++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 129 insertions(+), 4 deletions(-)

diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt
index 9229d45..4749676 100644
--- a/Documentation/git-svn.txt
+++ b/Documentation/git-svn.txt
@@ -104,6 +104,10 @@ COMMANDS
 --parent;;
 	Fetch only from the SVN parent of the current HEAD.
 
+--bare;;
+	Creates bare repository without checked-out copy.  See section
+	BARE REPOSITORIES for details.
+
 This doesn't interfere with interoperating with the Subversion
 repository you cloned from, but if you wish for your local Git
 repository to be able to interoperate with someone else's local Git
@@ -537,6 +541,25 @@ and these settings should never be changed once they are set.
 Additionally, only one of these four options can be used per-svn-remote
 section because they affect the 'git-svn-id:' metadata line.
 
+BARE REPOSITORIES
+-----------------
+
+Bare repositories are most suitable for serving them via 'git-daemon',
+so they can be used when one wants to convert Subversion repository to
+Git and base the work on the created Git repository.  This was the
+initial idea of adding support for such repositories: repository is
+thought to be read-only for Git clients, but could be updated via 'git
+svn fetch' to catch changes in Subversion repository.
+
+Only subset of all 'git svn' commands are available for bare
+repositories: init, clone, fetch, log, find-rev, show-ignore,
+show-externals, create-ignore, propget, proplist and info.
+
+One certainly could push to bare repository that is maintained by
+'git-svn', but merges are not supported on the bare repositories, so it
+could be hard to recover from such push.  The bottom line: currently
+supported mode is read-only for all Git clients and only this mode is
+now tested and supported.  Git wizards are, of course, free for all.
 
 BASIC EXAMPLES
 --------------
diff --git a/git-svn.perl b/git-svn.perl
index c5965c9..db6037f 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -63,13 +63,14 @@ $sha1 = qr/[a-f\d]{40}/;
 $sha1_short = qr/[a-f\d]{4,40}/;
 my ($_stdin, $_help, $_edit,
 	$_message, $_file,
-	$_template, $_shared,
+	$_template, $_shared, $_bare,
 	$_version, $_fetch_all, $_no_rebase, $_fetch_parent,
 	$_merge, $_strategy, $_dry_run, $_local,
 	$_prefix, $_no_checkout, $_url, $_verbose,
 	$_git_format, $_commit_url, $_tag);
 $Git::SVN::_follow_parent = 1;
 $_q ||= 0;
+$_bare = 0;
 my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username,
                     'config-dir=s' => \$Git::SVN::Ra::config_dir,
                     'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache,
@@ -96,6 +97,7 @@ my %init_opts = ( 'template=s' => \$_template, 'shared:s' => \$_shared,
                   'trunk|T=s' => \$_trunk, 'tags|t=s' => \$_tags,
                   'branches|b=s' => \$_branches, 'prefix=s' => \$_prefix,
                   'stdlayout|s' => \$_stdlayout,
+                  'bare' => \$_bare,
                   'minimize-url|m' => \$Git::SVN::_minimize_url,
 		  'no-metadata' => sub { $icv{noMetadata} = 1 },
 		  'use-svm-props' => sub { $icv{useSvmProps} = 1 },
@@ -315,7 +317,7 @@ sub version {
 }
 
 sub do_git_init_db {
-	unless (-d $ENV{GIT_DIR}) {
+	unless (-d $ENV{GIT_DIR} && !$_bare) {
 		my @init_db = ('init');
 		push @init_db, "--template=$_template" if defined $_template;
 		if (defined $_shared) {
@@ -325,8 +327,15 @@ sub do_git_init_db {
 				push @init_db, "--shared";
 			}
 		}
+		push @init_db, "--bare" if $_bare;
 		command_noisy(@init_db);
-		$_repository = Git->repository(Repository => ".git");
+		if ($_bare && -d $ENV{GIT_DIR}) {
+			$_repository =
+			    Git->repository(Repository => $ENV{GIT_DIR});
+		} else {
+			$_repository =
+			    Git->repository(Repository => ".git");
+		}
 	}
 	command_noisy('config', 'core.autocrlf', 'false');
 	my $set;
@@ -344,9 +353,20 @@ sub do_git_init_db {
 
 sub init_subdir {
 	my $repo_path = shift or return;
+	my $full_path;
+	if ($repo_path =~ /^\//) {
+		$full_path = $repo_path;
+	} else {
+		use POSIX qw(getcwd);
+		$full_path = POSIX::getcwd() . "/" . $repo_path;
+	}
 	mkpath([$repo_path]) unless -d $repo_path;
 	chdir $repo_path or die "Couldn't chdir to $repo_path: $!\n";
-	$ENV{GIT_DIR} = '.git';
+	if (!$_bare) {
+		$ENV{GIT_DIR} = $full_path . '/.git';
+	} else {
+		$ENV{GIT_DIR} = $full_path;
+	}
 	$_repository = Git->repository(Repository => $ENV{GIT_DIR});
 }
 
@@ -408,6 +428,9 @@ sub cmd_fetch {
 
 sub cmd_set_tree {
 	my (@commits) = @_;
+
+	fatal_if_bare($cmd);
+
 	if ($_stdin || !@commits) {
 		print "Reading from stdin...\n";
 		@commits = ();
@@ -444,6 +467,9 @@ sub cmd_set_tree {
 
 sub cmd_dcommit {
 	my $head = shift;
+
+	fatal_if_bare($cmd);
+
 	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";
@@ -579,6 +605,8 @@ sub cmd_dcommit {
 sub cmd_branch {
 	my ($branch_name, $head) = @_;
 
+	fatal_if_bare($cmd);
+
 	unless (defined $branch_name && length $branch_name) {
 		die(($_tag ? "tag" : "branch") . " name required\n");
 	}
@@ -635,6 +663,8 @@ sub cmd_find_rev {
 }
 
 sub cmd_rebase {
+	fatal_if_bare($cmd);
+
 	command_noisy(qw/update-index --refresh/);
 	my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
 	unless ($gs) {
@@ -793,6 +823,8 @@ sub cmd_proplist {
 }
 
 sub cmd_multi_init {
+	fatal_if_bare($cmd);
+
 	my $url = shift;
 	unless (defined $_trunk || defined $_branches || defined $_tags) {
 		usage(1);
@@ -836,6 +868,8 @@ sub cmd_multi_fetch {
 
 # this command is special because it requires no metadata
 sub cmd_commit_diff {
+	fatal_if_bare($cmd);
+
 	my ($ta, $tb, $url) = @_;
 	my $usage = "Usage: $0 commit-diff -r<revision> ".
 	            "<tree-ish> <tree-ish> [<URL>]";
@@ -1527,6 +1561,57 @@ sub parse_revision_argument {
 	die "revision argument: $::_revision not understood by git-svn\n";
 }
 
+#
+# While we are fetching to bare repositories, we should update branch
+# heads manually, because it is not possible to do merges within bare
+# repositories.
+#
+# Arguments:
+# - name of remote branch, for example 'refs/remotes/git-svn';
+# - name of the corresponding local head, for example 'refs/heads/master'.
+#
+sub fast_forward_bare_fetch {
+	return unless Git::config_bool('core.bare');
+	my ($remote, $localhead) = @_;
+
+	# Gather current SHA1 codes for both objects.
+	my $new_sha1ref = eval {
+		command_oneline(qw/show-ref --hash/, $remote)
+	};
+	return unless length($new_sha1ref);
+	my $current_sha1ref = eval {
+		command_oneline(qw/show-ref --hash/, $localhead)
+	};
+	return unless length($current_sha1ref);
+
+	# Same?  Nothing to do.
+	return if $new_sha1ref eq $current_sha1ref;
+
+	# Update SHA1 for local head and verify new value.
+	command_noisy('update-ref', $localhead, $new_sha1ref);
+	$current_sha1ref = eval {
+		command_oneline(qw/show-ref --hash/, $localhead)
+	};
+	if ($current_sha1ref eq $new_sha1ref) {
+		unless ($::_q > 1) {
+			printf "Fast-forwarded %s to %s.\n",
+			  $localhead, $new_sha1ref;
+		}
+	} else {
+		printf "Error fast forwarding %s to %s.\n",
+		  $localhead, $new_sha1ref;
+	}
+}
+
+sub fatal_if_bare {
+	my ($cmd) = @_;
+
+	if (!Git::config_bool('core.bare')) {
+		fatal(sprintf("'%s' isn't supported for bare repositories.",
+		  $cmd));
+	}
+}
+
 sub fetch_all {
 	my ($repo_id, $remotes) = @_;
 	if (ref $repo_id) {
@@ -1571,6 +1656,23 @@ sub fetch_all {
 
 	($base, $head) = parse_revision_argument($base, $head);
 	$ra->gs_fetch_loop_common($base, $head, \@gs, \@globs);
+
+	# Update (fast-forward) heads for bare repository.
+	if (Git::config_bool('core.bare')) {
+		foreach my $gs (@gs) {
+			my $remote = 'refs/remotes/' . $gs->{ref_id};
+			my $localhead = eval {
+			  command_oneline(qw/rev-parse
+			  --symbolic-full-name HEAD/)
+			};
+			if (length($localhead) == 0 ||
+			    $localhead eq "HEAD") {
+				die(sprintf("Unable to dereference HEAD " .
+				  "for %s"), $remote);
+			}
+			fast_forward_bare_fetch($remote, $localhead);
+		}
+	}
 }
 
 sub read_all_remotes {
-- 
1.6.2.4


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH] git-svn: teach to create and operate on bare repositories
  2009-04-22 21:36   ` Eygene Ryabinkin
@ 2009-04-23 18:21     ` Eric Wong
  0 siblings, 0 replies; 4+ messages in thread
From: Eric Wong @ 2009-04-23 18:21 UTC (permalink / raw)
  To: Eygene Ryabinkin; +Cc: git

Eygene Ryabinkin <rea-git@codelabs.ru> wrote:
> Eric, good day.
> 
> Tue, Apr 21, 2009 at 11:01:01AM -0700, Eric Wong wrote:
> > This definitely seems useful.  I'd like a basic test to ensure it
> > continues working in the future.
> 
> No problems, answers inline ;))
> 
> > >  sub cmd_set_tree {
> > >  	my (@commits) = @_;
> > > +
> > > +	get_bareness();
> > > +	if ($_bare) {
> > > +		fatal("'set-tree' isn't supported for bare repositories.");
> > > +	}
> > > +
> > 
> > This repetition with get_bareness() and then checking for $_bare all
> > over the place bothers me a bit.  I'd rather just have something like
> > this:
> > 
> > 	fatal_if_bare('set-tree');
> > 
> > Or even:
> > 
> > 	fatal_if_bare($cmd);
> 
> Done.
> 
> > > +# Initialize bareness flag for an existing repository
> > > +sub get_bareness {
> > > +	return unless -d $ENV{GIT_DIR};
> > > +	my $result = Git::config_bool('core.bare');
> > > +	$result = 0 unless defined($result);
> > > +	$_bare = $result;
> > > +}
> > 
> > Perhaps this function can go into Git.pm since core.bare affects all
> > of git, not just git-svn.  I'd also like to just use something like
> > 
> > 	if (Git::config_bare()) { ... }
> > 
> > ...rather than having to check/initialize a variable every time.
> > 
> > Of course, we can have config caching for platforms that really need
> > it done via Sam's Git::Config module when it gets merged.
> 
> Hmm, for my needs the bare "Git::config_bool('core.bare')" is sufficient,
> so I implanted it everywhere.
> 
> > >  sub extract_metadata {
> > >  	my $id = shift or return (undef, undef, undef);
> > >  	my ($url, $rev, $uuid) = ($id =~ /^\s*git-svn-id:\s+(.*)\@(\d+)
> > > @@ -1527,6 +1588,51 @@ sub parse_revision_argument {
> > >  	die "revision argument: $::_revision not understood by git-svn\n";
> > >  }
> > >  
> > > +#
> > > +# While we are fetching to bare repositories, we should update branch
> > > +# heads manually, because it is not possible to do merges within bare
> > > +# repositories.
> > > +#
> > > +# Arguments:
> > > +# - name of remote branch, for example 'refs/remotes/git-svn';
> > > +# - name of the corresponding local head, for example 'refs/heads/master'.
> > > +#
> > > +sub fast_forward_bare_fetch {
> > > +	return unless defined($_bare);
> > > +	return unless defined($_[0]);
> > > +	return unless defined($_[1]);
> > 
> > Checking for arguments here seems unnecessarily defensive.
> 
> I used to this style, but OK, it was eliminated.
> 
> > 
> > > +	my $remote = $_[0];
> > > +	my $localhead = $_[1];
> > 
> > The general git-svn style is this:
> > 
> > 	my ($remote, $localhead) = @_;
> 
> Fixed.
> 
> > > +	# Update (fast-forward) heads for bare repository.
> > > +	if (defined($_bare)) {
> > > +		foreach my $gs (@gs) {
> > > +			my $remote = 'refs/remotes/' . $gs->{ref_id};
> > > +			my $localhead = 'refs/heads/master';
> > > +			if (open(FH, "<", "HEAD")) {
> > > +				chomp(my $head = <FH>);
> > > +				close FH;
> > > +				if ($head =~ /^ref: (.*)$/) {
> > > +					$localhead = $1;
> > > +				}
> > > +			}
> > 
> > "git rev-parse --symbolic-full-name HEAD" should work better
> > than parsing HEAD ourselves.
> 
> Used it.  The resulting patch is attached.
> 
> I have one question: as you can see from the above hunk, I am using HEAD
> as the tip of all local branches to be fast-forwarded.  It works with
> only one upstream branch, but what if there will be multiple
> (disconnected) remotes we are tracking?  I had seen the words that it it
> possible (in the git-svn man page), but hadn't managed to create such
> repository and figure out how can I deduce the name of the local head
> for the remote one.  Could you, please, enlighten me?

For bare repositories, I would just update all the remotes/*
to heads/* or tags/*.  Perhaps:

	foreach my $gs (@gs) {
		my $remote = 'refs/remotes/' . $gs->{ref_id};
		my $pfx = $gs->{ref_id} =~ m{^tags/} ? 'tags' : 'heads';
		my $localhead = "refs/$pfx/$gs->{ref_id}";
		fast_forward_bare_fetch($remote, $localhead);
	}

I'm not sure what should be done with HEAD, it's been brought up for
discussion once again...

clone/init currently picks the latest modified branch/trunk/tag because:

a) it's the easiest

b) some repositories I tracked did all the work in branches
   so trunk almost always out-of-date.

Also, can you make sure any patches you send actually passes the test
suite (or fix the test suite so it passes)?

Thanks.

-- 
Eric Wong

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2009-04-23 18:22 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2009-04-19 17:57 [PATCH] git-svn: teach to create and operate on bare repositories Eygene Ryabinkin
2009-04-21 18:01 ` Eric Wong
2009-04-22 21:36   ` Eygene Ryabinkin
2009-04-23 18:21     ` Eric Wong

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.