{"thread":{"id":"18956","subject":"[PATCH] git-svn: teach to create and operate on bare repositories","startedAt":"2009-04-19T17:57:54Z","lastAt":"2009-04-23T18:21:10Z","messageCount":4,"participants":["Eygene Ryabinkin","Eric Wong"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"111655","messageId":"lGoUDENjg0Z+sDE9v/txzTLyfk8@7qgLKkvX/1U6eu9avhKQpU/1pEI","threadId":"18956","inReplyTo":null,"subject":"[PATCH] git-svn: teach to create and operate on bare repositories","fromName":"Eygene Ryabinkin","fromEmail":"rea-git@codelabs.ru","sentAt":"2009-04-19T17:57:54Z","receivedAt":"2009-04-19T17:57:54Z","isPatch":true,"sender":{"key":"rea-git@codelabs.ru","avatar":null},"body":"This mode is useful when we're mirrorring one-to-one Subversion and Git\nrepositories and then using Git as the repository to base other work on.\n\nBare mode is currently restricted only to init, clone, fetch, log,\nfind-rev, show-ignore, show-externals, create-ignore, propget, proplist\nand info commands.  Others won't work at all and error message will\nbe produced.\n\nSigned-off-by: Eygene Ryabinkin <rea-git@codelabs.ru>\n---\n Documentation/git-svn.txt |   23 ++++++++\n git-svn.perl              |  130 +++++++++++++++++++++++++++++++++++++++++++--\n 2 files changed, 149 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt\nindex 9229d45..4749676 100644\n--- a/Documentation/git-svn.txt\n+++ b/Documentation/git-svn.txt\n@@ -104,6 +104,10 @@ COMMANDS\n --parent;;\n \tFetch only from the SVN parent of the current HEAD.\n \n+--bare;;\n+\tCreates bare repository without checked-out copy.  See section\n+\tBARE REPOSITORIES for details.\n+\n This doesn't interfere with interoperating with the Subversion\n repository you cloned from, but if you wish for your local Git\n repository to be able to interoperate with someone else's local Git\n@@ -537,6 +541,25 @@ and these settings should never be changed once they are set.\n Additionally, only one of these four options can be used per-svn-remote\n section because they affect the 'git-svn-id:' metadata line.\n \n+BARE REPOSITORIES\n+-----------------\n+\n+Bare repositories are most suitable for serving them via 'git-daemon',\n+so they can be used when one wants to convert Subversion repository to\n+Git and base the work on the created Git repository.  This was the\n+initial idea of adding support for such repositories: repository is\n+thought to be read-only for Git clients, but could be updated via 'git\n+svn fetch' to catch changes in Subversion repository.\n+\n+Only subset of all 'git svn' commands are available for bare\n+repositories: init, clone, fetch, log, find-rev, show-ignore,\n+show-externals, create-ignore, propget, proplist and info.\n+\n+One certainly could push to bare repository that is maintained by\n+'git-svn', but merges are not supported on the bare repositories, so it\n+could be hard to recover from such push.  The bottom line: currently\n+supported mode is read-only for all Git clients and only this mode is\n+now tested and supported.  Git wizards are, of course, free for all.\n \n BASIC EXAMPLES\n --------------\ndiff --git a/git-svn.perl b/git-svn.perl\nindex c5965c9..4756abf 100755\n--- a/git-svn.perl\n+++ b/git-svn.perl\n@@ -63,13 +63,14 @@ $sha1 = qr/[a-f\\d]{40}/;\n $sha1_short = qr/[a-f\\d]{4,40}/;\n my ($_stdin, $_help, $_edit,\n \t$_message, $_file,\n-\t$_template, $_shared,\n+\t$_template, $_shared, $_bare,\n \t$_version, $_fetch_all, $_no_rebase, $_fetch_parent,\n \t$_merge, $_strategy, $_dry_run, $_local,\n \t$_prefix, $_no_checkout, $_url, $_verbose,\n \t$_git_format, $_commit_url, $_tag);\n $Git::SVN::_follow_parent = 1;\n $_q ||= 0;\n+$_bare = 0;\n my %remote_opts = ( 'username=s' => \\$Git::SVN::Prompt::_username,\n                     'config-dir=s' => \\$Git::SVN::Ra::config_dir,\n                     'no-auth-cache' => \\$Git::SVN::Prompt::_no_auth_cache,\n@@ -96,6 +97,7 @@ my %init_opts = ( 'template=s' => \\$_template, 'shared:s' => \\$_shared,\n                   'trunk|T=s' => \\$_trunk, 'tags|t=s' => \\$_tags,\n                   'branches|b=s' => \\$_branches, 'prefix=s' => \\$_prefix,\n                   'stdlayout|s' => \\$_stdlayout,\n+                  'bare' => \\$_bare,\n                   'minimize-url|m' => \\$Git::SVN::_minimize_url,\n \t\t  'no-metadata' => sub { $icv{noMetadata} = 1 },\n \t\t  'use-svm-props' => sub { $icv{useSvmProps} = 1 },\n@@ -315,7 +317,7 @@ sub version {\n }\n \n sub do_git_init_db {\n-\tunless (-d $ENV{GIT_DIR}) {\n+\tunless (-d $ENV{GIT_DIR} && !$_bare) {\n \t\tmy @init_db = ('init');\n \t\tpush @init_db, \"--template=$_template\" if defined $_template;\n \t\tif (defined $_shared) {\n@@ -325,8 +327,15 @@ sub do_git_init_db {\n \t\t\t\tpush @init_db, \"--shared\";\n \t\t\t}\n \t\t}\n+\t\tpush @init_db, \"--bare\" if $_bare;\n \t\tcommand_noisy(@init_db);\n-\t\t$_repository = Git->repository(Repository => \".git\");\n+\t\tif ($_bare && -d $ENV{GIT_DIR}) {\n+\t\t\t$_repository =\n+\t\t\t    Git->repository(Repository => $ENV{GIT_DIR});\n+\t\t} else {\n+\t\t\t$_repository =\n+\t\t\t    Git->repository(Repository => \".git\");\n+\t\t}\n \t}\n \tcommand_noisy('config', 'core.autocrlf', 'false');\n \tmy $set;\n@@ -344,9 +353,20 @@ sub do_git_init_db {\n \n sub init_subdir {\n \tmy $repo_path = shift or return;\n+\tmy $full_path;\n+\tif ($repo_path =~ /^\\//) {\n+\t\t$full_path = $repo_path;\n+\t} else {\n+\t\tuse POSIX qw(getcwd);\n+\t\t$full_path = POSIX::getcwd() . \"/\" . $repo_path;\n+\t}\n \tmkpath([$repo_path]) unless -d $repo_path;\n \tchdir $repo_path or die \"Couldn't chdir to $repo_path: $!\\n\";\n-\t$ENV{GIT_DIR} = '.git';\n+\tif (!$_bare) {\n+\t\t$ENV{GIT_DIR} = $full_path . '/.git';\n+\t} else {\n+\t\t$ENV{GIT_DIR} = $full_path;\n+\t}\n \t$_repository = Git->repository(Repository => $ENV{GIT_DIR});\n }\n \n@@ -389,6 +409,7 @@ sub cmd_fetch {\n \tif (@_ > 1) {\n \t\tdie \"Usage: $0 fetch [--all] [--parent] [svn-remote]\\n\";\n \t}\n+\tget_bareness();\n \tif ($_fetch_parent) {\n \t\tmy ($url, $rev, $uuid, $gs) = working_head_info('HEAD');\n \t\tunless ($gs) {\n@@ -408,6 +429,12 @@ sub cmd_fetch {\n \n sub cmd_set_tree {\n \tmy (@commits) = @_;\n+\n+\tget_bareness();\n+\tif ($_bare) {\n+\t\tfatal(\"'set-tree' isn't supported for bare repositories.\");\n+\t}\n+\n \tif ($_stdin || !@commits) {\n \t\tprint \"Reading from stdin...\\n\";\n \t\t@commits = ();\n@@ -444,6 +471,12 @@ sub cmd_set_tree {\n \n sub cmd_dcommit {\n \tmy $head = shift;\n+\n+\tget_bareness();\n+\tif ($_bare) {\n+\t\tfatal(\"'dcommit' isn't supported for bare repositories.\");\n+\t}\n+\n \tgit_cmd_try { command_oneline(qw/diff-index --quiet HEAD/) }\n \t\t'Cannot dcommit with a dirty index.  Commit your changes first, '\n \t\t. \"or stash them with `git stash'.\\n\";\n@@ -579,6 +612,11 @@ sub cmd_dcommit {\n sub cmd_branch {\n \tmy ($branch_name, $head) = @_;\n \n+\tget_bareness();\n+\tif ($_bare) {\n+\t\tfatal(\"'branch' isn't supported for bare repositories.\");\n+\t}\n+\n \tunless (defined $branch_name && length $branch_name) {\n \t\tdie(($_tag ? \"tag\" : \"branch\") . \" name required\\n\");\n \t}\n@@ -635,6 +673,11 @@ sub cmd_find_rev {\n }\n \n sub cmd_rebase {\n+\tget_bareness();\n+\tif ($_bare) {\n+\t\tfatal(\"'rebase' isn't supported for bare repositories.\");\n+\t}\n+\n \tcommand_noisy(qw/update-index --refresh/);\n \tmy ($url, $rev, $uuid, $gs) = working_head_info('HEAD');\n \tunless ($gs) {\n@@ -793,6 +836,11 @@ sub cmd_proplist {\n }\n \n sub cmd_multi_init {\n+\tget_bareness();\n+\tif ($_bare) {\n+\t\tfatal(\"'multi-init' isn't supported for bare repositories.\");\n+\t}\n+\n \tmy $url = shift;\n \tunless (defined $_trunk || defined $_branches || defined $_tags) {\n \t\tusage(1);\n@@ -836,6 +884,11 @@ sub cmd_multi_fetch {\n \n # this command is special because it requires no metadata\n sub cmd_commit_diff {\n+\tget_bareness();\n+\tif ($_bare) {\n+\t\tfatal(\"'commit-diff' isn't supported for bare repositories.\");\n+\t}\n+\n \tmy ($ta, $tb, $url) = @_;\n \tmy $usage = \"Usage: $0 commit-diff -r<revision> \".\n \t            \"<tree-ish> <tree-ish> [<URL>]\";\n@@ -1252,6 +1305,14 @@ sub read_repo_config {\n \tdelete @$opts{@config_only} if @config_only;\n }\n \n+# Initialize bareness flag for an existing repository\n+sub get_bareness {\n+\treturn unless -d $ENV{GIT_DIR};\n+\tmy $result = Git::config_bool('core.bare');\n+\t$result = 0 unless defined($result);\n+\t$_bare = $result;\n+}\n+\n sub extract_metadata {\n \tmy $id = shift or return (undef, undef, undef);\n \tmy ($url, $rev, $uuid) = ($id =~ /^\\s*git-svn-id:\\s+(.*)\\@(\\d+)\n@@ -1527,6 +1588,51 @@ sub parse_revision_argument {\n \tdie \"revision argument: $::_revision not understood by git-svn\\n\";\n }\n \n+#\n+# While we are fetching to bare repositories, we should update branch\n+# heads manually, because it is not possible to do merges within bare\n+# repositories.\n+#\n+# Arguments:\n+# - name of remote branch, for example 'refs/remotes/git-svn';\n+# - name of the corresponding local head, for example 'refs/heads/master'.\n+#\n+sub fast_forward_bare_fetch {\n+\treturn unless defined($_bare);\n+\treturn unless defined($_[0]);\n+\treturn unless defined($_[1]);\n+\tmy $remote = $_[0];\n+\tmy $localhead = $_[1];\n+\n+\t# Gather current SHA1 codes for both objects.\n+\tmy $new_sha1ref = eval {\n+\t\tcommand_oneline(qw/show-ref --hash/, $remote)\n+\t};\n+\treturn unless length($new_sha1ref);\n+\tmy $current_sha1ref = eval {\n+\t\tcommand_oneline(qw/show-ref --hash/, $localhead)\n+\t};\n+\treturn unless length($current_sha1ref);\n+\n+\t# Same?  Nothing to do.\n+\treturn if $new_sha1ref eq $current_sha1ref;\n+\n+\t# Update SHA1 for local head and verify new value.\n+\tcommand_noisy('update-ref', $localhead, $new_sha1ref);\n+\t$current_sha1ref = eval {\n+\t\tcommand_oneline(qw/show-ref --hash/, $localhead)\n+\t};\n+\tif ($current_sha1ref eq $new_sha1ref) {\n+\t\tunless ($::_q > 1) {\n+\t\t\tprintf \"Fast-forwarded %s to %s.\\n\",\n+\t\t  \t  $localhead, $new_sha1ref;\n+\t\t}\n+\t} else {\n+\t\tprintf \"Error fast forwarding %s to %s.\\n\",\n+\t\t  $localhead, $new_sha1ref;\n+\t}\n+}\n+\n sub fetch_all {\n \tmy ($repo_id, $remotes) = @_;\n \tif (ref $repo_id) {\n@@ -1571,6 +1677,22 @@ sub fetch_all {\n \n \t($base, $head) = parse_revision_argument($base, $head);\n \t$ra->gs_fetch_loop_common($base, $head, \\@gs, \\@globs);\n+\n+\t# Update (fast-forward) heads for bare repository.\n+\tif (defined($_bare)) {\n+\t\tforeach my $gs (@gs) {\n+\t\t\tmy $remote = 'refs/remotes/' . $gs->{ref_id};\n+\t\t\tmy $localhead = 'refs/heads/master';\n+\t\t\tif (open(FH, \"<\", \"HEAD\")) {\n+\t\t\t\tchomp(my $head = <FH>);\n+\t\t\t\tclose FH;\n+\t\t\t\tif ($head =~ /^ref: (.*)$/) {\n+\t\t\t\t\t$localhead = $1;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tfast_forward_bare_fetch($remote, $localhead);\n+\t\t}\n+\t}\n }\n \n sub read_all_remotes {\n-- \n1.6.1.3\n"},{"id":"111890","messageId":"20090421180101.GA16642@dcvr.yhbt.net","threadId":"18956","inReplyTo":"lGoUDENjg0Z+sDE9v/txzTLyfk8@7qgLKkvX/1U6eu9avhKQpU/1pEI","subject":"Re: [PATCH] git-svn: teach to create and operate on bare repositories","fromName":"Eric Wong","fromEmail":"normalperson@yhbt.net","sentAt":"2009-04-21T18:01:01Z","receivedAt":"2009-04-21T18:01:01Z","isPatch":true,"sender":{"key":"e@80x24.org","avatar":null},"body":"Eygene Ryabinkin <rea-git@codelabs.ru> wrote:\n> This mode is useful when we're mirrorring one-to-one Subversion and Git\n> repositories and then using Git as the repository to base other work on.\n> \n> Bare mode is currently restricted only to init, clone, fetch, log,\n> find-rev, show-ignore, show-externals, create-ignore, propget, proplist\n> and info commands.  Others won't work at all and error message will\n> be produced.\n\nHi Eygene,\n\nThis definitely seems useful.  I'd like a basic test to ensure it\ncontinues working in the future.\n\nMore comments inline.\n\n> Signed-off-by: Eygene Ryabinkin <rea-git@codelabs.ru>\n\n> index c5965c9..4756abf 100755\n> --- a/git-svn.perl\n> +++ b/git-svn.perl\n> @@ -63,13 +63,14 @@ $sha1 = qr/[a-f\\d]{40}/;\n>  $sha1_short = qr/[a-f\\d]{4,40}/;\n>  my ($_stdin, $_help, $_edit,\n>  \t$_message, $_file,\n> -\t$_template, $_shared,\n> +\t$_template, $_shared, $_bare,\n>  \t$_version, $_fetch_all, $_no_rebase, $_fetch_parent,\n>  \t$_merge, $_strategy, $_dry_run, $_local,\n>  \t$_prefix, $_no_checkout, $_url, $_verbose,\n>  \t$_git_format, $_commit_url, $_tag);\n>  $Git::SVN::_follow_parent = 1;\n>  $_q ||= 0;\n> +$_bare = 0;\n>  my %remote_opts = ( 'username=s' => \\$Git::SVN::Prompt::_username,\n>                      'config-dir=s' => \\$Git::SVN::Ra::config_dir,\n>                      'no-auth-cache' => \\$Git::SVN::Prompt::_no_auth_cache,\n\n>  }\n>  \n>  sub do_git_init_db {\n> -\tunless (-d $ENV{GIT_DIR}) {\n> +\tunless (-d $ENV{GIT_DIR} && !$_bare) {\n>  \t\tmy @init_db = ('init');\n>  \t\tpush @init_db, \"--template=$_template\" if defined $_template;\n>  \t\tif (defined $_shared) {\n> @@ -325,8 +327,15 @@ sub do_git_init_db {\n>  \t\t\t\tpush @init_db, \"--shared\";\n>  \t\t\t}\n>  \t\t}\n> +\t\tpush @init_db, \"--bare\" if $_bare;\n>  \t\tcommand_noisy(@init_db);\n> -\t\t$_repository = Git->repository(Repository => \".git\");\n> +\t\tif ($_bare && -d $ENV{GIT_DIR}) {\n> +\t\t\t$_repository =\n> +\t\t\t    Git->repository(Repository => $ENV{GIT_DIR});\n> +\t\t} else {\n> +\t\t\t$_repository =\n> +\t\t\t    Git->repository(Repository => \".git\");\n> +\t\t}\n>  \t}\n>  \tcommand_noisy('config', 'core.autocrlf', 'false');\n>  \tmy $set;\n> @@ -344,9 +353,20 @@ sub do_git_init_db {\n>  \n>  sub init_subdir {\n>  \tmy $repo_path = shift or return;\n> +\tmy $full_path;\n> +\tif ($repo_path =~ /^\\//) {\n> +\t\t$full_path = $repo_path;\n> +\t} else {\n> +\t\tuse POSIX qw(getcwd);\n> +\t\t$full_path = POSIX::getcwd() . \"/\" . $repo_path;\n> +\t}\n>  \tmkpath([$repo_path]) unless -d $repo_path;\n>  \tchdir $repo_path or die \"Couldn't chdir to $repo_path: $!\\n\";\n> -\t$ENV{GIT_DIR} = '.git';\n> +\tif (!$_bare) {\n> +\t\t$ENV{GIT_DIR} = $full_path . '/.git';\n> +\t} else {\n> +\t\t$ENV{GIT_DIR} = $full_path;\n> +\t}\n>  \t$_repository = Git->repository(Repository => $ENV{GIT_DIR});\n>  }\n>  \n> @@ -389,6 +409,7 @@ sub cmd_fetch {\n>  \tif (@_ > 1) {\n>  \t\tdie \"Usage: $0 fetch [--all] [--parent] [svn-remote]\\n\";\n>  \t}\n> +\tget_bareness();\n>  \tif ($_fetch_parent) {\n>  \t\tmy ($url, $rev, $uuid, $gs) = working_head_info('HEAD');\n>  \t\tunless ($gs) {\n> @@ -408,6 +429,12 @@ sub cmd_fetch {\n>  \n>  sub cmd_set_tree {\n>  \tmy (@commits) = @_;\n> +\n> +\tget_bareness();\n> +\tif ($_bare) {\n> +\t\tfatal(\"'set-tree' isn't supported for bare repositories.\");\n> +\t}\n> +\n\nThis repetition with get_bareness() and then checking for $_bare all\nover the place bothers me a bit.  I'd rather just have something like\nthis:\n\n\tfatal_if_bare('set-tree');\n\nOr even:\n\n\tfatal_if_bare($cmd);\n\n> +# Initialize bareness flag for an existing repository\n> +sub get_bareness {\n> +\treturn unless -d $ENV{GIT_DIR};\n> +\tmy $result = Git::config_bool('core.bare');\n> +\t$result = 0 unless defined($result);\n> +\t$_bare = $result;\n> +}\n\nPerhaps this function can go into Git.pm since core.bare affects all\nof git, not just git-svn.  I'd also like to just use something like\n\n\tif (Git::config_bare()) { ... }\n\n...rather than having to check/initialize a variable every time.\n\nOf course, we can have config caching for platforms that really need\nit done via Sam's Git::Config module when it gets merged.\n\n>  sub extract_metadata {\n>  \tmy $id = shift or return (undef, undef, undef);\n>  \tmy ($url, $rev, $uuid) = ($id =~ /^\\s*git-svn-id:\\s+(.*)\\@(\\d+)\n> @@ -1527,6 +1588,51 @@ sub parse_revision_argument {\n>  \tdie \"revision argument: $::_revision not understood by git-svn\\n\";\n>  }\n>  \n> +#\n> +# While we are fetching to bare repositories, we should update branch\n> +# heads manually, because it is not possible to do merges within bare\n> +# repositories.\n> +#\n> +# Arguments:\n> +# - name of remote branch, for example 'refs/remotes/git-svn';\n> +# - name of the corresponding local head, for example 'refs/heads/master'.\n> +#\n> +sub fast_forward_bare_fetch {\n> +\treturn unless defined($_bare);\n> +\treturn unless defined($_[0]);\n> +\treturn unless defined($_[1]);\n\nChecking for arguments here seems unnecessarily defensive.\n\n> +\tmy $remote = $_[0];\n> +\tmy $localhead = $_[1];\n\nThe general git-svn style is this:\n\n\tmy ($remote, $localhead) = @_;\n\n> +\t# Update (fast-forward) heads for bare repository.\n> +\tif (defined($_bare)) {\n> +\t\tforeach my $gs (@gs) {\n> +\t\t\tmy $remote = 'refs/remotes/' . $gs->{ref_id};\n> +\t\t\tmy $localhead = 'refs/heads/master';\n> +\t\t\tif (open(FH, \"<\", \"HEAD\")) {\n> +\t\t\t\tchomp(my $head = <FH>);\n> +\t\t\t\tclose FH;\n> +\t\t\t\tif ($head =~ /^ref: (.*)$/) {\n> +\t\t\t\t\t$localhead = $1;\n> +\t\t\t\t}\n> +\t\t\t}\n\n\"git rev-parse --symbolic-full-name HEAD\" should work better\nthan parsing HEAD ourselves.\n\nThanks!\n\n-- \nEric Wong\n"},{"id":"112019","messageId":"qxrtBtZrSZuDTP4eMtsCu+KmGTY@urRFFPz6LpPjhjTBiIOEhMtLGGg","threadId":"18956","inReplyTo":"20090421180101.GA16642@dcvr.yhbt.net","subject":"Re: [PATCH] git-svn: teach to create and operate on bare repositories","fromName":"Eygene Ryabinkin","fromEmail":"rea-git@codelabs.ru","sentAt":"2009-04-22T21:36:28Z","receivedAt":"2009-04-22T21:36:28Z","isPatch":true,"sender":{"key":"rea-git@codelabs.ru","avatar":null},"body":"Eric, good day.\n\nTue, Apr 21, 2009 at 11:01:01AM -0700, Eric Wong wrote:\n> This definitely seems useful.  I'd like a basic test to ensure it\n> continues working in the future.\n\nNo problems, answers inline ;))\n\n> >  sub cmd_set_tree {\n> >  \tmy (@commits) = @_;\n> > +\n> > +\tget_bareness();\n> > +\tif ($_bare) {\n> > +\t\tfatal(\"'set-tree' isn't supported for bare repositories.\");\n> > +\t}\n> > +\n> \n> This repetition with get_bareness() and then checking for $_bare all\n> over the place bothers me a bit.  I'd rather just have something like\n> this:\n> \n> \tfatal_if_bare('set-tree');\n> \n> Or even:\n> \n> \tfatal_if_bare($cmd);\n\nDone.\n\n> > +# Initialize bareness flag for an existing repository\n> > +sub get_bareness {\n> > +\treturn unless -d $ENV{GIT_DIR};\n> > +\tmy $result = Git::config_bool('core.bare');\n> > +\t$result = 0 unless defined($result);\n> > +\t$_bare = $result;\n> > +}\n> \n> Perhaps this function can go into Git.pm since core.bare affects all\n> of git, not just git-svn.  I'd also like to just use something like\n> \n> \tif (Git::config_bare()) { ... }\n> \n> ...rather than having to check/initialize a variable every time.\n> \n> Of course, we can have config caching for platforms that really need\n> it done via Sam's Git::Config module when it gets merged.\n\nHmm, for my needs the bare \"Git::config_bool('core.bare')\" is sufficient,\nso I implanted it everywhere.\n\n> >  sub extract_metadata {\n> >  \tmy $id = shift or return (undef, undef, undef);\n> >  \tmy ($url, $rev, $uuid) = ($id =~ /^\\s*git-svn-id:\\s+(.*)\\@(\\d+)\n> > @@ -1527,6 +1588,51 @@ sub parse_revision_argument {\n> >  \tdie \"revision argument: $::_revision not understood by git-svn\\n\";\n> >  }\n> >  \n> > +#\n> > +# While we are fetching to bare repositories, we should update branch\n> > +# heads manually, because it is not possible to do merges within bare\n> > +# repositories.\n> > +#\n> > +# Arguments:\n> > +# - name of remote branch, for example 'refs/remotes/git-svn';\n> > +# - name of the corresponding local head, for example 'refs/heads/master'.\n> > +#\n> > +sub fast_forward_bare_fetch {\n> > +\treturn unless defined($_bare);\n> > +\treturn unless defined($_[0]);\n> > +\treturn unless defined($_[1]);\n> \n> Checking for arguments here seems unnecessarily defensive.\n\nI used to this style, but OK, it was eliminated.\n\n> \n> > +\tmy $remote = $_[0];\n> > +\tmy $localhead = $_[1];\n> \n> The general git-svn style is this:\n> \n> \tmy ($remote, $localhead) = @_;\n\nFixed.\n\n> > +\t# Update (fast-forward) heads for bare repository.\n> > +\tif (defined($_bare)) {\n> > +\t\tforeach my $gs (@gs) {\n> > +\t\t\tmy $remote = 'refs/remotes/' . $gs->{ref_id};\n> > +\t\t\tmy $localhead = 'refs/heads/master';\n> > +\t\t\tif (open(FH, \"<\", \"HEAD\")) {\n> > +\t\t\t\tchomp(my $head = <FH>);\n> > +\t\t\t\tclose FH;\n> > +\t\t\t\tif ($head =~ /^ref: (.*)$/) {\n> > +\t\t\t\t\t$localhead = $1;\n> > +\t\t\t\t}\n> > +\t\t\t}\n> \n> \"git rev-parse --symbolic-full-name HEAD\" should work better\n> than parsing HEAD ourselves.\n\nUsed it.  The resulting patch is attached.\n\nI have one question: as you can see from the above hunk, I am using HEAD\nas the tip of all local branches to be fast-forwarded.  It works with\nonly one upstream branch, but what if there will be multiple\n(disconnected) remotes we are tracking?  I had seen the words that it it\npossible (in the git-svn man page), but hadn't managed to create such\nrepository and figure out how can I deduce the name of the local head\nfor the remote one.  Could you, please, enlighten me?\n\nThanks!\n-- \nEygene\n\n\nFrom cc509782cfa3c17dc830015891cde2a11275a2c4 Mon Sep 17 00:00:00 2001\nFrom: Eygene Ryabinkin <rea@codelabs.ru>\nDate: Thu, 16 Apr 2009 02:27:24 +0400\nSubject: [PATCH] git-svn: teach to create and operate on bare repositories\n\nThis mode is useful when we're mirrorring one-to-one Subversion and Git\nrepositories and then using Git as the repository to base other work on.\n\nBare mode is currently restricted only to init, clone, fetch, log,\nfind-rev, show-ignore, show-externals, create-ignore, propget, proplist\nand info commands.  Others won't work at all and error message will\nbe produced.\n\nSigned-off-by: Eygene Ryabinkin <rea-git@codelabs.ru>\n---\n Documentation/git-svn.txt |   23 +++++++++\n git-svn.perl              |  110 +++++++++++++++++++++++++++++++++++++++++++--\n 2 files changed, 129 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt\nindex 9229d45..4749676 100644\n--- a/Documentation/git-svn.txt\n+++ b/Documentation/git-svn.txt\n@@ -104,6 +104,10 @@ COMMANDS\n --parent;;\n \tFetch only from the SVN parent of the current HEAD.\n \n+--bare;;\n+\tCreates bare repository without checked-out copy.  See section\n+\tBARE REPOSITORIES for details.\n+\n This doesn't interfere with interoperating with the Subversion\n repository you cloned from, but if you wish for your local Git\n repository to be able to interoperate with someone else's local Git\n@@ -537,6 +541,25 @@ and these settings should never be changed once they are set.\n Additionally, only one of these four options can be used per-svn-remote\n section because they affect the 'git-svn-id:' metadata line.\n \n+BARE REPOSITORIES\n+-----------------\n+\n+Bare repositories are most suitable for serving them via 'git-daemon',\n+so they can be used when one wants to convert Subversion repository to\n+Git and base the work on the created Git repository.  This was the\n+initial idea of adding support for such repositories: repository is\n+thought to be read-only for Git clients, but could be updated via 'git\n+svn fetch' to catch changes in Subversion repository.\n+\n+Only subset of all 'git svn' commands are available for bare\n+repositories: init, clone, fetch, log, find-rev, show-ignore,\n+show-externals, create-ignore, propget, proplist and info.\n+\n+One certainly could push to bare repository that is maintained by\n+'git-svn', but merges are not supported on the bare repositories, so it\n+could be hard to recover from such push.  The bottom line: currently\n+supported mode is read-only for all Git clients and only this mode is\n+now tested and supported.  Git wizards are, of course, free for all.\n \n BASIC EXAMPLES\n --------------\ndiff --git a/git-svn.perl b/git-svn.perl\nindex c5965c9..db6037f 100755\n--- a/git-svn.perl\n+++ b/git-svn.perl\n@@ -63,13 +63,14 @@ $sha1 = qr/[a-f\\d]{40}/;\n $sha1_short = qr/[a-f\\d]{4,40}/;\n my ($_stdin, $_help, $_edit,\n \t$_message, $_file,\n-\t$_template, $_shared,\n+\t$_template, $_shared, $_bare,\n \t$_version, $_fetch_all, $_no_rebase, $_fetch_parent,\n \t$_merge, $_strategy, $_dry_run, $_local,\n \t$_prefix, $_no_checkout, $_url, $_verbose,\n \t$_git_format, $_commit_url, $_tag);\n $Git::SVN::_follow_parent = 1;\n $_q ||= 0;\n+$_bare = 0;\n my %remote_opts = ( 'username=s' => \\$Git::SVN::Prompt::_username,\n                     'config-dir=s' => \\$Git::SVN::Ra::config_dir,\n                     'no-auth-cache' => \\$Git::SVN::Prompt::_no_auth_cache,\n@@ -96,6 +97,7 @@ my %init_opts = ( 'template=s' => \\$_template, 'shared:s' => \\$_shared,\n                   'trunk|T=s' => \\$_trunk, 'tags|t=s' => \\$_tags,\n                   'branches|b=s' => \\$_branches, 'prefix=s' => \\$_prefix,\n                   'stdlayout|s' => \\$_stdlayout,\n+                  'bare' => \\$_bare,\n                   'minimize-url|m' => \\$Git::SVN::_minimize_url,\n \t\t  'no-metadata' => sub { $icv{noMetadata} = 1 },\n \t\t  'use-svm-props' => sub { $icv{useSvmProps} = 1 },\n@@ -315,7 +317,7 @@ sub version {\n }\n \n sub do_git_init_db {\n-\tunless (-d $ENV{GIT_DIR}) {\n+\tunless (-d $ENV{GIT_DIR} && !$_bare) {\n \t\tmy @init_db = ('init');\n \t\tpush @init_db, \"--template=$_template\" if defined $_template;\n \t\tif (defined $_shared) {\n@@ -325,8 +327,15 @@ sub do_git_init_db {\n \t\t\t\tpush @init_db, \"--shared\";\n \t\t\t}\n \t\t}\n+\t\tpush @init_db, \"--bare\" if $_bare;\n \t\tcommand_noisy(@init_db);\n-\t\t$_repository = Git->repository(Repository => \".git\");\n+\t\tif ($_bare && -d $ENV{GIT_DIR}) {\n+\t\t\t$_repository =\n+\t\t\t    Git->repository(Repository => $ENV{GIT_DIR});\n+\t\t} else {\n+\t\t\t$_repository =\n+\t\t\t    Git->repository(Repository => \".git\");\n+\t\t}\n \t}\n \tcommand_noisy('config', 'core.autocrlf', 'false');\n \tmy $set;\n@@ -344,9 +353,20 @@ sub do_git_init_db {\n \n sub init_subdir {\n \tmy $repo_path = shift or return;\n+\tmy $full_path;\n+\tif ($repo_path =~ /^\\//) {\n+\t\t$full_path = $repo_path;\n+\t} else {\n+\t\tuse POSIX qw(getcwd);\n+\t\t$full_path = POSIX::getcwd() . \"/\" . $repo_path;\n+\t}\n \tmkpath([$repo_path]) unless -d $repo_path;\n \tchdir $repo_path or die \"Couldn't chdir to $repo_path: $!\\n\";\n-\t$ENV{GIT_DIR} = '.git';\n+\tif (!$_bare) {\n+\t\t$ENV{GIT_DIR} = $full_path . '/.git';\n+\t} else {\n+\t\t$ENV{GIT_DIR} = $full_path;\n+\t}\n \t$_repository = Git->repository(Repository => $ENV{GIT_DIR});\n }\n \n@@ -408,6 +428,9 @@ sub cmd_fetch {\n \n sub cmd_set_tree {\n \tmy (@commits) = @_;\n+\n+\tfatal_if_bare($cmd);\n+\n \tif ($_stdin || !@commits) {\n \t\tprint \"Reading from stdin...\\n\";\n \t\t@commits = ();\n@@ -444,6 +467,9 @@ sub cmd_set_tree {\n \n sub cmd_dcommit {\n \tmy $head = shift;\n+\n+\tfatal_if_bare($cmd);\n+\n \tgit_cmd_try { command_oneline(qw/diff-index --quiet HEAD/) }\n \t\t'Cannot dcommit with a dirty index.  Commit your changes first, '\n \t\t. \"or stash them with `git stash'.\\n\";\n@@ -579,6 +605,8 @@ sub cmd_dcommit {\n sub cmd_branch {\n \tmy ($branch_name, $head) = @_;\n \n+\tfatal_if_bare($cmd);\n+\n \tunless (defined $branch_name && length $branch_name) {\n \t\tdie(($_tag ? \"tag\" : \"branch\") . \" name required\\n\");\n \t}\n@@ -635,6 +663,8 @@ sub cmd_find_rev {\n }\n \n sub cmd_rebase {\n+\tfatal_if_bare($cmd);\n+\n \tcommand_noisy(qw/update-index --refresh/);\n \tmy ($url, $rev, $uuid, $gs) = working_head_info('HEAD');\n \tunless ($gs) {\n@@ -793,6 +823,8 @@ sub cmd_proplist {\n }\n \n sub cmd_multi_init {\n+\tfatal_if_bare($cmd);\n+\n \tmy $url = shift;\n \tunless (defined $_trunk || defined $_branches || defined $_tags) {\n \t\tusage(1);\n@@ -836,6 +868,8 @@ sub cmd_multi_fetch {\n \n # this command is special because it requires no metadata\n sub cmd_commit_diff {\n+\tfatal_if_bare($cmd);\n+\n \tmy ($ta, $tb, $url) = @_;\n \tmy $usage = \"Usage: $0 commit-diff -r<revision> \".\n \t            \"<tree-ish> <tree-ish> [<URL>]\";\n@@ -1527,6 +1561,57 @@ sub parse_revision_argument {\n \tdie \"revision argument: $::_revision not understood by git-svn\\n\";\n }\n \n+#\n+# While we are fetching to bare repositories, we should update branch\n+# heads manually, because it is not possible to do merges within bare\n+# repositories.\n+#\n+# Arguments:\n+# - name of remote branch, for example 'refs/remotes/git-svn';\n+# - name of the corresponding local head, for example 'refs/heads/master'.\n+#\n+sub fast_forward_bare_fetch {\n+\treturn unless Git::config_bool('core.bare');\n+\tmy ($remote, $localhead) = @_;\n+\n+\t# Gather current SHA1 codes for both objects.\n+\tmy $new_sha1ref = eval {\n+\t\tcommand_oneline(qw/show-ref --hash/, $remote)\n+\t};\n+\treturn unless length($new_sha1ref);\n+\tmy $current_sha1ref = eval {\n+\t\tcommand_oneline(qw/show-ref --hash/, $localhead)\n+\t};\n+\treturn unless length($current_sha1ref);\n+\n+\t# Same?  Nothing to do.\n+\treturn if $new_sha1ref eq $current_sha1ref;\n+\n+\t# Update SHA1 for local head and verify new value.\n+\tcommand_noisy('update-ref', $localhead, $new_sha1ref);\n+\t$current_sha1ref = eval {\n+\t\tcommand_oneline(qw/show-ref --hash/, $localhead)\n+\t};\n+\tif ($current_sha1ref eq $new_sha1ref) {\n+\t\tunless ($::_q > 1) {\n+\t\t\tprintf \"Fast-forwarded %s to %s.\\n\",\n+\t\t\t  $localhead, $new_sha1ref;\n+\t\t}\n+\t} else {\n+\t\tprintf \"Error fast forwarding %s to %s.\\n\",\n+\t\t  $localhead, $new_sha1ref;\n+\t}\n+}\n+\n+sub fatal_if_bare {\n+\tmy ($cmd) = @_;\n+\n+\tif (!Git::config_bool('core.bare')) {\n+\t\tfatal(sprintf(\"'%s' isn't supported for bare repositories.\",\n+\t\t  $cmd));\n+\t}\n+}\n+\n sub fetch_all {\n \tmy ($repo_id, $remotes) = @_;\n \tif (ref $repo_id) {\n@@ -1571,6 +1656,23 @@ sub fetch_all {\n \n \t($base, $head) = parse_revision_argument($base, $head);\n \t$ra->gs_fetch_loop_common($base, $head, \\@gs, \\@globs);\n+\n+\t# Update (fast-forward) heads for bare repository.\n+\tif (Git::config_bool('core.bare')) {\n+\t\tforeach my $gs (@gs) {\n+\t\t\tmy $remote = 'refs/remotes/' . $gs->{ref_id};\n+\t\t\tmy $localhead = eval {\n+\t\t\t  command_oneline(qw/rev-parse\n+\t\t\t  --symbolic-full-name HEAD/)\n+\t\t\t};\n+\t\t\tif (length($localhead) == 0 ||\n+\t\t\t    $localhead eq \"HEAD\") {\n+\t\t\t\tdie(sprintf(\"Unable to dereference HEAD \" .\n+\t\t\t\t  \"for %s\"), $remote);\n+\t\t\t}\n+\t\t\tfast_forward_bare_fetch($remote, $localhead);\n+\t\t}\n+\t}\n }\n \n sub read_all_remotes {\n-- \n1.6.2.4\n\n"},{"id":"112105","messageId":"20090423182110.GA17467@dcvr.yhbt.net","threadId":"18956","inReplyTo":"qxrtBtZrSZuDTP4eMtsCu+KmGTY@urRFFPz6LpPjhjTBiIOEhMtLGGg","subject":"Re: [PATCH] git-svn: teach to create and operate on bare repositories","fromName":"Eric Wong","fromEmail":"normalperson@yhbt.net","sentAt":"2009-04-23T18:21:10Z","receivedAt":"2009-04-23T18:21:10Z","isPatch":true,"sender":{"key":"e@80x24.org","avatar":null},"body":"Eygene Ryabinkin <rea-git@codelabs.ru> wrote:\n> Eric, good day.\n> \n> Tue, Apr 21, 2009 at 11:01:01AM -0700, Eric Wong wrote:\n> > This definitely seems useful.  I'd like a basic test to ensure it\n> > continues working in the future.\n> \n> No problems, answers inline ;))\n> \n> > >  sub cmd_set_tree {\n> > >  \tmy (@commits) = @_;\n> > > +\n> > > +\tget_bareness();\n> > > +\tif ($_bare) {\n> > > +\t\tfatal(\"'set-tree' isn't supported for bare repositories.\");\n> > > +\t}\n> > > +\n> > \n> > This repetition with get_bareness() and then checking for $_bare all\n> > over the place bothers me a bit.  I'd rather just have something like\n> > this:\n> > \n> > \tfatal_if_bare('set-tree');\n> > \n> > Or even:\n> > \n> > \tfatal_if_bare($cmd);\n> \n> Done.\n> \n> > > +# Initialize bareness flag for an existing repository\n> > > +sub get_bareness {\n> > > +\treturn unless -d $ENV{GIT_DIR};\n> > > +\tmy $result = Git::config_bool('core.bare');\n> > > +\t$result = 0 unless defined($result);\n> > > +\t$_bare = $result;\n> > > +}\n> > \n> > Perhaps this function can go into Git.pm since core.bare affects all\n> > of git, not just git-svn.  I'd also like to just use something like\n> > \n> > \tif (Git::config_bare()) { ... }\n> > \n> > ...rather than having to check/initialize a variable every time.\n> > \n> > Of course, we can have config caching for platforms that really need\n> > it done via Sam's Git::Config module when it gets merged.\n> \n> Hmm, for my needs the bare \"Git::config_bool('core.bare')\" is sufficient,\n> so I implanted it everywhere.\n> \n> > >  sub extract_metadata {\n> > >  \tmy $id = shift or return (undef, undef, undef);\n> > >  \tmy ($url, $rev, $uuid) = ($id =~ /^\\s*git-svn-id:\\s+(.*)\\@(\\d+)\n> > > @@ -1527,6 +1588,51 @@ sub parse_revision_argument {\n> > >  \tdie \"revision argument: $::_revision not understood by git-svn\\n\";\n> > >  }\n> > >  \n> > > +#\n> > > +# While we are fetching to bare repositories, we should update branch\n> > > +# heads manually, because it is not possible to do merges within bare\n> > > +# repositories.\n> > > +#\n> > > +# Arguments:\n> > > +# - name of remote branch, for example 'refs/remotes/git-svn';\n> > > +# - name of the corresponding local head, for example 'refs/heads/master'.\n> > > +#\n> > > +sub fast_forward_bare_fetch {\n> > > +\treturn unless defined($_bare);\n> > > +\treturn unless defined($_[0]);\n> > > +\treturn unless defined($_[1]);\n> > \n> > Checking for arguments here seems unnecessarily defensive.\n> \n> I used to this style, but OK, it was eliminated.\n> \n> > \n> > > +\tmy $remote = $_[0];\n> > > +\tmy $localhead = $_[1];\n> > \n> > The general git-svn style is this:\n> > \n> > \tmy ($remote, $localhead) = @_;\n> \n> Fixed.\n> \n> > > +\t# Update (fast-forward) heads for bare repository.\n> > > +\tif (defined($_bare)) {\n> > > +\t\tforeach my $gs (@gs) {\n> > > +\t\t\tmy $remote = 'refs/remotes/' . $gs->{ref_id};\n> > > +\t\t\tmy $localhead = 'refs/heads/master';\n> > > +\t\t\tif (open(FH, \"<\", \"HEAD\")) {\n> > > +\t\t\t\tchomp(my $head = <FH>);\n> > > +\t\t\t\tclose FH;\n> > > +\t\t\t\tif ($head =~ /^ref: (.*)$/) {\n> > > +\t\t\t\t\t$localhead = $1;\n> > > +\t\t\t\t}\n> > > +\t\t\t}\n> > \n> > \"git rev-parse --symbolic-full-name HEAD\" should work better\n> > than parsing HEAD ourselves.\n> \n> Used it.  The resulting patch is attached.\n> \n> I have one question: as you can see from the above hunk, I am using HEAD\n> as the tip of all local branches to be fast-forwarded.  It works with\n> only one upstream branch, but what if there will be multiple\n> (disconnected) remotes we are tracking?  I had seen the words that it it\n> possible (in the git-svn man page), but hadn't managed to create such\n> repository and figure out how can I deduce the name of the local head\n> for the remote one.  Could you, please, enlighten me?\n\nFor bare repositories, I would just update all the remotes/*\nto heads/* or tags/*.  Perhaps:\n\n\tforeach my $gs (@gs) {\n\t\tmy $remote = 'refs/remotes/' . $gs->{ref_id};\n\t\tmy $pfx = $gs->{ref_id} =~ m{^tags/} ? 'tags' : 'heads';\n\t\tmy $localhead = \"refs/$pfx/$gs->{ref_id}\";\n\t\tfast_forward_bare_fetch($remote, $localhead);\n\t}\n\nI'm not sure what should be done with HEAD, it's been brought up for\ndiscussion once again...\n\nclone/init currently picks the latest modified branch/trunk/tag because:\n\na) it's the easiest\n\nb) some repositories I tracked did all the work in branches\n   so trunk almost always out-of-date.\n\nAlso, can you make sure any patches you send actually passes the test\nsuite (or fix the test suite so it passes)?\n\nThanks.\n\n-- \nEric Wong\n"}]}