Merge branch 'sr/transport-helper-fix'

* sr/transport-helper-fix: (21 commits)
  transport-helper: die early on encountering deleted refs
  transport-helper: implement marks location as capability
  transport-helper: Use capname for refspec capability too
  transport-helper: change import semantics
  transport-helper: update ref status after push with export
  transport-helper: use the new done feature where possible
  transport-helper: check status code of finish_command
  transport-helper: factor out push_update_refs_status
  fast-export: support done feature
  fast-import: introduce 'done' command
  git-remote-testgit: fix error handling
  git-remote-testgit: only push for non-local repositories
  remote-curl: accept empty line as terminator
  remote-helpers: export GIT_DIR variable to helpers
  git_remote_helpers: push all refs during a non-local export
  transport-helper: don't feed bogus refs to export push
  git-remote-testgit: import non-HEAD refs
  t5800: document some non-functional parts of remote helpers
  t5800: use skip_all instead of prereq
  t5800: factor out some ref tests
  ...
This commit is contained in:
Junio C Hamano 2011-08-01 15:00:14 -07:00
commit 59d9ba869e
15 changed files with 475 additions and 187 deletions

View file

@ -83,6 +83,10 @@ marks the same across runs.
allow that. So fake a tagger to be able to fast-import the
output.
--use-done-feature::
Start the stream with a 'feature done' stanza, and terminate
it with a 'done' command.
--no-data::
Skip output of blob objects and instead refer to blobs via
their original SHA-1 hash. This is useful when rewriting the

View file

@ -102,6 +102,12 @@ OPTIONS
when the `cat-blob` command is encountered in the stream.
The default behaviour is to write to `stdout`.
--done::
Require a `done` command at the end of the stream.
This option might be useful for detecting errors that
cause the frontend to terminate before it has started to
write a stream.
--export-pack-edges=<file>::
After creating a packfile, print a line of data to
<file> listing the filename of the packfile and the last
@ -331,6 +337,11 @@ and control the current import process. More detailed discussion
standard output. This command is optional and is not needed
to perform an import.
`done`::
Marks the end of the stream. This command is optional
unless the `done` feature was requested using the
`--done` command line option or `feature done` command.
`cat-blob`::
Causes fast-import to print a blob in 'cat-file --batch'
format to the file descriptor set with `--cat-blob-fd` or
@ -1021,6 +1032,11 @@ notes::
Versions of fast-import not supporting notes will exit
with a message indicating so.
done::
Error out if the stream ends without a 'done' command.
Without this feature, errors causing the frontend to end
abruptly at a convenient point in the stream can go
undetected.
`option`
~~~~~~~~
@ -1050,6 +1066,15 @@ not be passed as option:
* cat-blob-fd
* force
`done`
~~~~~~
If the `done` feature is not in use, treated as if EOF was read.
This can be used to tell fast-import to finish early.
If the `--done` command line option or `feature done` command is
in use, the `done` command is mandatory and marks the end of the
stream.
Crash Reports
-------------
If fast-import is supplied invalid input it will terminate with a

View file

@ -48,6 +48,9 @@ arguments. The first argument specifies a remote repository as in git;
it is either the name of a configured remote or a URL. The second
argument specifies a URL; it is usually of the form
'<transport>://<address>', but any arbitrary string is possible.
The 'GIT_DIR' environment variable is set up for the remote helper
and can be used to determine where to store additional data or from
which directory to invoke auxiliary git commands.
When git encounters a URL of the form '<transport>://<address>', where
'<transport>' is a protocol that it cannot handle natively, it

View file

@ -26,6 +26,7 @@ static int progress;
static enum { ABORT, VERBATIM, WARN, STRIP } signed_tag_mode = ABORT;
static enum { ERROR, DROP, REWRITE } tag_of_filtered_mode = ABORT;
static int fake_missing_tagger;
static int use_done_feature;
static int no_data;
static int full_tree;
@ -627,6 +628,8 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
"Fake a tagger when tags lack one"),
OPT_BOOLEAN(0, "full-tree", &full_tree,
"Output full tree for each commit"),
OPT_BOOLEAN(0, "use-done-feature", &use_done_feature,
"Use the done feature to terminate the stream"),
{ OPTION_NEGBIT, 0, "data", &no_data, NULL,
"Skip output of blob data",
PARSE_OPT_NOARG | PARSE_OPT_NEGHELP, NULL, 1 },
@ -648,6 +651,9 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
if (argc > 1)
usage_with_options (fast_export_usage, options);
if (use_done_feature)
printf("feature done\n");
if (import_filename)
import_marks(import_filename);
@ -675,5 +681,8 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
if (export_filename)
export_marks(export_filename);
if (use_done_feature)
printf("done\n");
return 0;
}

View file

@ -355,6 +355,7 @@ static unsigned int cmd_save = 100;
static uintmax_t next_mark;
static struct strbuf new_data = STRBUF_INIT;
static int seen_data_command;
static int require_explicit_termination;
/* Signal handling */
static volatile sig_atomic_t checkpoint_requested;
@ -3140,6 +3141,8 @@ static int parse_one_feature(const char *feature, int from_stream)
relative_marks_paths = 1;
} else if (!strcmp(feature, "no-relative-marks")) {
relative_marks_paths = 0;
} else if (!strcmp(feature, "done")) {
require_explicit_termination = 1;
} else if (!strcmp(feature, "force")) {
force_update = 1;
} else if (!strcmp(feature, "notes") || !strcmp(feature, "ls")) {
@ -3290,6 +3293,8 @@ int main(int argc, const char **argv)
parse_reset_branch();
else if (!strcmp("checkpoint", command_buf.buf))
parse_checkpoint();
else if (!strcmp("done", command_buf.buf))
break;
else if (!prefixcmp(command_buf.buf, "progress "))
parse_progress();
else if (!prefixcmp(command_buf.buf, "feature "))
@ -3309,6 +3314,9 @@ int main(int argc, const char **argv)
if (!seen_data_command)
parse_argv();
if (require_explicit_termination && feof(stdin))
die("stream ends early");
end_packfile();
dump_branches();

View file

@ -35,7 +35,7 @@ def get_repo(alias, url):
prefix = 'refs/testgit/%s/' % alias
debug("prefix: '%s'", prefix)
repo.gitdir = ""
repo.gitdir = os.environ["GIT_DIR"]
repo.alias = alias
repo.prefix = prefix
@ -70,9 +70,19 @@ def do_capabilities(repo, args):
print "import"
print "export"
print "gitdir"
print "refspec refs/heads/*:%s*" % repo.prefix
dirname = repo.get_base_path(repo.gitdir)
if not os.path.exists(dirname):
os.makedirs(dirname)
path = os.path.join(dirname, 'testgit.marks')
print "*export-marks %s" % path
if os.path.exists(path):
print "*import-marks %s" % path
print # end capabilities
@ -121,8 +131,24 @@ def do_import(repo, args):
if not repo.gitdir:
die("Need gitdir to import")
ref = args[0]
refs = [ref]
while True:
line = sys.stdin.readline()
if line == '\n':
break
if not line.startswith('import '):
die("Expected import line.")
# strip of leading 'import '
ref = line[7:].strip()
refs.append(ref)
repo = update_local_repo(repo)
repo.exporter.export_repo(repo.gitdir)
repo.exporter.export_repo(repo.gitdir, refs)
print "done"
def do_export(repo, args):
@ -132,32 +158,15 @@ def do_export(repo, args):
if not repo.gitdir:
die("Need gitdir to export")
dirname = repo.get_base_path(repo.gitdir)
if not os.path.exists(dirname):
os.makedirs(dirname)
path = os.path.join(dirname, 'testgit.marks')
print path
if os.path.exists(path):
print path
else:
print ""
sys.stdout.flush()
update_local_repo(repo)
repo.importer.do_import(repo.gitdir)
repo.non_local.push(repo.gitdir)
changed = repo.importer.do_import(repo.gitdir)
if not repo.local:
repo.non_local.push(repo.gitdir)
def do_gitdir(repo, args):
"""Stores the location of the gitdir.
"""
if not args:
die("gitdir needs an argument")
repo.gitdir = ' '.join(args)
for ref in changed:
print "ok %s" % ref
print
COMMANDS = {
@ -165,7 +174,6 @@ def do_gitdir(repo, args):
'list': do_list,
'import': do_import,
'export': do_export,
'gitdir': do_gitdir,
}

View file

@ -2,6 +2,8 @@
import subprocess
import sys
from git_remote_helpers.util import check_call
class GitExporter(object):
"""An exporter for testgit repositories.
@ -15,7 +17,7 @@ def __init__(self, repo):
self.repo = repo
def export_repo(self, base):
def export_repo(self, base, refs=None):
"""Exports a fast-export stream for the given directory.
Simply delegates to git fast-epxort and pipes it through sed
@ -23,8 +25,13 @@ def export_repo(self, base):
default refs/heads. This is to demonstrate how the export
data can be stored under it's own ref (using the refspec
capability).
If None, refs defaults to ["HEAD"].
"""
if not refs:
refs = ["HEAD"]
dirname = self.repo.get_base_path(base)
path = os.path.abspath(os.path.join(dirname, 'testgit.marks'))
@ -42,12 +49,10 @@ def export_repo(self, base):
if os.path.exists(path):
args.append("--import-marks=" + path)
args.append("HEAD")
args.extend(refs)
p1 = subprocess.Popen(args, stdout=subprocess.PIPE)
args = ["sed", "s_refs/heads/_" + self.repo.prefix + "_g"]
child = subprocess.Popen(args, stdin=p1.stdout)
if child.wait() != 0:
raise CalledProcessError
check_call(args, stdin=p1.stdout)

View file

@ -1,6 +1,8 @@
import os
import subprocess
from git_remote_helpers.util import check_call, check_output
class GitImporter(object):
"""An importer for testgit repositories.
@ -14,6 +16,18 @@ def __init__(self, repo):
self.repo = repo
def get_refs(self, gitdir):
"""Returns a dictionary with refs.
"""
args = ["git", "--git-dir=" + gitdir, "for-each-ref", "refs/heads"]
lines = check_output(args).strip().split('\n')
refs = {}
for line in lines:
value, name = line.split(' ')
name = name.strip('commit\t')
refs[name] = value
return refs
def do_import(self, base):
"""Imports a fast-import stream to the given directory.
@ -30,11 +44,23 @@ def do_import(self, base):
if not os.path.exists(dirname):
os.makedirs(dirname)
refs_before = self.get_refs(gitdir)
args = ["git", "--git-dir=" + gitdir, "fast-import", "--quiet", "--export-marks=" + path]
if os.path.exists(path):
args.append("--import-marks=" + path)
child = subprocess.Popen(args)
if child.wait() != 0:
raise CalledProcessError
check_call(args)
refs_after = self.get_refs(gitdir)
changed = {}
for name, value in refs_after.iteritems():
if refs_before.get(name) == value:
continue
changed[name] = value
return changed

View file

@ -1,7 +1,7 @@
import os
import subprocess
from git_remote_helpers.util import die, warn
from git_remote_helpers.util import check_call, die, warn
class NonLocalGit(object):
@ -29,9 +29,7 @@ def clone(self, base):
os.makedirs(path)
args = ["git", "clone", "--bare", "--quiet", self.repo.gitpath, path]
child = subprocess.Popen(args)
if child.wait() != 0:
raise CalledProcessError
check_call(args)
return path
@ -45,14 +43,10 @@ def update(self, base):
die("could not find repo at %s", path)
args = ["git", "--git-dir=" + path, "fetch", "--quiet", self.repo.gitpath]
child = subprocess.Popen(args)
if child.wait() != 0:
raise CalledProcessError
check_call(args)
args = ["git", "--git-dir=" + path, "update-ref", "refs/heads/master", "FETCH_HEAD"]
child = subprocess.Popen(args)
if child.wait() != 0:
raise CalledProcessError
child = check_call(args)
def push(self, base):
"""Pushes from the non-local repo to base.
@ -63,7 +57,5 @@ def push(self, base):
if not os.path.exists(path):
die("could not find repo at %s", path)
args = ["git", "--git-dir=" + path, "push", "--quiet", self.repo.gitpath]
child = subprocess.Popen(args)
if child.wait() != 0:
raise CalledProcessError
args = ["git", "--git-dir=" + path, "push", "--quiet", self.repo.gitpath, "--all"]
child = check_call(args)

View file

@ -1,6 +1,9 @@
import os
import subprocess
from git_remote_helpers.util import check_call
def sanitize(rev, sep='\t'):
"""Converts a for-each-ref line to a name/value pair.
"""
@ -53,9 +56,7 @@ def get_revs(self):
path = ".cached_revs"
ofile = open(path, "w")
child = subprocess.Popen(args, stdout=ofile)
if child.wait() != 0:
raise CalledProcessError
check_call(args, stdout=ofile)
output = open(path).readlines()
self.revmap = dict(sanitize(i) for i in output)
if "HEAD" in self.revmap:

View file

@ -11,6 +11,21 @@
import os
import subprocess
try:
from subprocess import CalledProcessError
except ImportError:
# from python2.7:subprocess.py
# Exception classes used by this module.
class CalledProcessError(Exception):
"""This exception is raised when a process run by check_call() returns
a non-zero exit status. The exit status will be stored in the
returncode attribute."""
def __init__(self, returncode, cmd):
self.returncode = returncode
self.cmd = cmd
def __str__(self):
return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
# Whether or not to show debug messages
DEBUG = False
@ -128,6 +143,72 @@ def run_command (args, cwd = None, shell = False, add_env = None,
return (exit_code, output, errors)
# from python2.7:subprocess.py
def call(*popenargs, **kwargs):
"""Run command with arguments. Wait for command to complete, then
return the returncode attribute.
The arguments are the same as for the Popen constructor. Example:
retcode = call(["ls", "-l"])
"""
return subprocess.Popen(*popenargs, **kwargs).wait()
# from python2.7:subprocess.py
def check_call(*popenargs, **kwargs):
"""Run command with arguments. Wait for command to complete. If
the exit code was zero then return, otherwise raise
CalledProcessError. The CalledProcessError object will have the
return code in the returncode attribute.
The arguments are the same as for the Popen constructor. Example:
check_call(["ls", "-l"])
"""
retcode = call(*popenargs, **kwargs)
if retcode:
cmd = kwargs.get("args")
if cmd is None:
cmd = popenargs[0]
raise CalledProcessError(retcode, cmd)
return 0
# from python2.7:subprocess.py
def check_output(*popenargs, **kwargs):
r"""Run command with arguments and return its output as a byte string.
If the exit code was non-zero it raises a CalledProcessError. The
CalledProcessError object will have the return code in the returncode
attribute and output in the output attribute.
The arguments are the same as for the Popen constructor. Example:
>>> check_output(["ls", "-l", "/dev/null"])
'crw-rw-rw- 1 root root 1, 3 Oct 18 2007 /dev/null\n'
The stdout argument is not allowed as it is used internally.
To capture standard error in the result, use stderr=STDOUT.
>>> check_output(["/bin/sh", "-c",
... "ls -l non_existent_file ; exit 0"],
... stderr=STDOUT)
'ls: non_existent_file: No such file or directory\n'
"""
if 'stdout' in kwargs:
raise ValueError('stdout argument not allowed, it will be overridden.')
process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
output, unused_err = process.communicate()
retcode = process.poll()
if retcode:
cmd = kwargs.get("args")
if cmd is None:
cmd = popenargs[0]
raise subprocess.CalledProcessError(retcode, cmd)
return output
def file_reader_method (missing_ok = False):
"""Decorator for simplifying reading of files.

View file

@ -855,7 +855,14 @@ int main(int argc, const char **argv)
http_init(remote);
do {
if (strbuf_getline(&buf, stdin, '\n') == EOF)
if (strbuf_getline(&buf, stdin, '\n') == EOF) {
if (ferror(stdin))
fprintf(stderr, "Error reading command stream\n");
else
fprintf(stderr, "Unexpected end of command stream\n");
return 1;
}
if (buf.len == 0)
break;
if (!prefixcmp(buf.buf, "fetch ")) {
if (nongit)
@ -895,6 +902,7 @@ int main(int argc, const char **argv)
printf("\n");
fflush(stdout);
} else {
fprintf(stderr, "Unknown command '%s'\n", buf.buf);
return 1;
}
strbuf_reset(&buf);

View file

@ -7,17 +7,27 @@ test_description='Test remote-helper import and export commands'
. ./test-lib.sh
if test_have_prereq PYTHON && "$PYTHON_PATH" -c '
if ! test_have_prereq PYTHON ; then
skip_all='skipping git-remote-hg tests, python not available'
test_done
fi
"$PYTHON_PATH" -c '
import sys
if sys.hexversion < 0x02040000:
sys.exit(1)
'
then
# Requires Python 2.4 or newer
test_set_prereq PYTHON_24
fi
' || {
skip_all='skipping git-remote-hg tests, python version < 2.4'
test_done
}
test_expect_success PYTHON_24 'setup repository' '
compare_refs() {
git --git-dir="$1/.git" rev-parse --verify $2 >expect &&
git --git-dir="$3/.git" rev-parse --verify $4 >actual &&
test_cmp expect actual
}
test_expect_success 'setup repository' '
git init --bare server/.git &&
git clone server public &&
(cd public &&
@ -27,54 +37,99 @@ test_expect_success PYTHON_24 'setup repository' '
git push origin master)
'
test_expect_success PYTHON_24 'cloning from local repo' '
test_expect_success 'cloning from local repo' '
git clone "testgit::${PWD}/server" localclone &&
test_cmp public/file localclone/file
'
test_expect_success PYTHON_24 'cloning from remote repo' '
test_expect_success 'cloning from remote repo' '
git clone "testgit::file://${PWD}/server" clone &&
test_cmp public/file clone/file
'
test_expect_success PYTHON_24 'create new commit on remote' '
test_expect_success 'create new commit on remote' '
(cd public &&
echo content >>file &&
git commit -a -m two &&
git push)
'
test_expect_success PYTHON_24 'pulling from local repo' '
test_expect_success 'pulling from local repo' '
(cd localclone && git pull) &&
test_cmp public/file localclone/file
'
test_expect_success PYTHON_24 'pulling from remote remote' '
test_expect_success 'pulling from remote remote' '
(cd clone && git pull) &&
test_cmp public/file clone/file
'
test_expect_success PYTHON_24 'pushing to local repo' '
test_expect_success 'pushing to local repo' '
(cd localclone &&
echo content >>file &&
git commit -a -m three &&
git push) &&
HEAD=$(git --git-dir=localclone/.git rev-parse --verify HEAD) &&
test $HEAD = $(git --git-dir=server/.git rev-parse --verify HEAD)
compare_refs localclone HEAD server HEAD
'
test_expect_success PYTHON_24 'synch with changes from localclone' '
test_expect_success 'synch with changes from localclone' '
(cd clone &&
git pull)
'
test_expect_success PYTHON_24 'pushing remote local repo' '
test_expect_success 'pushing remote local repo' '
(cd clone &&
echo content >>file &&
git commit -a -m four &&
git push) &&
HEAD=$(git --git-dir=clone/.git rev-parse --verify HEAD) &&
test $HEAD = $(git --git-dir=server/.git rev-parse --verify HEAD)
compare_refs clone HEAD server HEAD
'
test_expect_success 'fetch new branch' '
(cd public &&
git checkout -b new &&
echo content >>file &&
git commit -a -m five &&
git push origin new
) &&
(cd localclone &&
git fetch origin new
) &&
compare_refs public HEAD localclone FETCH_HEAD
'
test_expect_success 'fetch multiple branches' '
(cd localclone &&
git fetch
) &&
compare_refs server master localclone refs/remotes/origin/master &&
compare_refs server new localclone refs/remotes/origin/new
'
test_expect_success 'push when remote has extra refs' '
(cd clone &&
echo content >>file &&
git commit -a -m six &&
git push
) &&
compare_refs clone master server master
'
test_expect_success 'push new branch by name' '
(cd clone &&
git checkout -b new-name &&
echo content >>file &&
git commit -a -m seven &&
git push origin new-name
) &&
compare_refs clone HEAD server refs/heads/new-name
'
test_expect_failure 'push new branch with old:new refspec' '
(cd clone &&
git push origin new-name:new-refspec
) &&
compare_refs clone HEAD server refs/heads/new-refspec
'
test_done

View file

@ -2197,6 +2197,48 @@ test_expect_success 'R: quiet option results in no stats being output' '
test_cmp empty output
'
test_expect_success 'R: feature done means terminating "done" is mandatory' '
echo feature done | test_must_fail git fast-import &&
test_must_fail git fast-import --done </dev/null
'
test_expect_success 'R: terminating "done" with trailing gibberish is ok' '
git fast-import <<-\EOF &&
feature done
done
trailing gibberish
EOF
git fast-import <<-\EOF
done
more trailing gibberish
EOF
'
test_expect_success 'R: terminating "done" within commit' '
cat >expect <<-\EOF &&
OBJID
:000000 100644 OBJID OBJID A hello.c
:000000 100644 OBJID OBJID A hello2.c
EOF
git fast-import <<-EOF &&
commit refs/heads/done-ends
committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
data <<EOT
Commit terminated by "done" command
EOT
M 100644 inline hello.c
data <<EOT
Hello, world.
EOT
C hello.c hello2.c
done
EOF
git rev-list done-ends |
git diff-tree -r --stdin --root --always |
sed -e "s/$_x40/OBJID/g" >actual &&
test_cmp expect actual
'
cat >input <<EOF
option git non-existing-option
EOF

View file

@ -23,6 +23,8 @@ struct helper_data {
push : 1,
connect : 1,
no_disconnect_req : 1;
char *export_marks;
char *import_marks;
/* These go from remote name (as in "list") to private name */
struct refspec *refspecs;
int refspec_nr;
@ -105,6 +107,12 @@ static struct child_process *get_helper(struct transport *transport)
int refspec_alloc = 0;
int duped;
int code;
char git_dir_buf[sizeof(GIT_DIR_ENVIRONMENT) + PATH_MAX + 1];
const char *helper_env[] = {
git_dir_buf,
NULL
};
if (data->helper)
return data->helper;
@ -120,6 +128,10 @@ static struct child_process *get_helper(struct transport *transport)
helper->argv[2] = remove_ext_force(transport->url);
helper->git_cmd = 0;
helper->silent_exec_failure = 1;
snprintf(git_dir_buf, sizeof(git_dir_buf), "%s=%s", GIT_DIR_ENVIRONMENT, get_git_dir());
helper->env = helper_env;
code = start_command(helper);
if (code < 0 && errno == ENOENT)
die("Unable to find remote helper for '%s'", data->name);
@ -171,14 +183,19 @@ static struct child_process *get_helper(struct transport *transport)
ALLOC_GROW(refspecs,
refspec_nr + 1,
refspec_alloc);
refspecs[refspec_nr++] = strdup(buf.buf + strlen("refspec "));
refspecs[refspec_nr++] = strdup(capname + strlen("refspec "));
} else if (!strcmp(capname, "connect")) {
data->connect = 1;
} else if (!strcmp(buf.buf, "gitdir")) {
struct strbuf gitdir = STRBUF_INIT;
strbuf_addf(&gitdir, "gitdir %s\n", get_git_dir());
sendline(data, &gitdir);
strbuf_release(&gitdir);
} else if (!prefixcmp(capname, "export-marks ")) {
struct strbuf arg = STRBUF_INIT;
strbuf_addstr(&arg, "--export-marks=");
strbuf_addstr(&arg, capname + strlen("export-marks "));
data->export_marks = strbuf_detach(&arg, NULL);
} else if (!prefixcmp(capname, "import-marks")) {
struct strbuf arg = STRBUF_INIT;
strbuf_addstr(&arg, "--import-marks=");
strbuf_addstr(&arg, capname + strlen("import-marks "));
data->import_marks = strbuf_detach(&arg, NULL);
} else if (mandatory) {
die("Unknown mandatory capability %s. This remote "
"helper probably needs newer version of Git.\n",
@ -204,6 +221,7 @@ static int disconnect_helper(struct transport *transport)
{
struct helper_data *data = transport->data;
struct strbuf buf = STRBUF_INIT;
int res = 0;
if (data->helper) {
if (debug)
@ -215,13 +233,13 @@ static int disconnect_helper(struct transport *transport)
close(data->helper->in);
close(data->helper->out);
fclose(data->out);
finish_command(data->helper);
res = finish_command(data->helper);
free((char *)data->helper->argv[0]);
free(data->helper->argv);
free(data->helper);
data->helper = NULL;
}
return 0;
return res;
}
static const char *unsupported_options[] = {
@ -299,12 +317,13 @@ static void standard_options(struct transport *t)
static int release_helper(struct transport *transport)
{
int res = 0;
struct helper_data *data = transport->data;
free_refspec(data->refspec_nr, data->refspecs);
data->refspecs = NULL;
disconnect_helper(transport);
res = disconnect_helper(transport);
free(transport->data);
return 0;
return res;
}
static int fetch_with_fetch(struct transport *transport,
@ -362,10 +381,9 @@ static int get_importer(struct transport *transport, struct child_process *fasti
static int get_exporter(struct transport *transport,
struct child_process *fastexport,
const char *export_marks,
const char *import_marks,
struct string_list *revlist_args)
{
struct helper_data *data = transport->data;
struct child_process *helper = get_helper(transport);
int argc = 0, i;
memset(fastexport, 0, sizeof(*fastexport));
@ -373,12 +391,13 @@ static int get_exporter(struct transport *transport,
/* we need to duplicate helper->in because we want to use it after
* fastexport is done with it. */
fastexport->out = dup(helper->in);
fastexport->argv = xcalloc(4 + revlist_args->nr, sizeof(*fastexport->argv));
fastexport->argv = xcalloc(5 + revlist_args->nr, sizeof(*fastexport->argv));
fastexport->argv[argc++] = "fast-export";
if (export_marks)
fastexport->argv[argc++] = export_marks;
if (import_marks)
fastexport->argv[argc++] = import_marks;
fastexport->argv[argc++] = "--use-done-feature";
if (data->export_marks)
fastexport->argv[argc++] = data->export_marks;
if (data->import_marks)
fastexport->argv[argc++] = data->import_marks;
for (i = 0; i < revlist_args->nr; i++)
fastexport->argv[argc++] = revlist_args->items[i].string;
@ -410,8 +429,11 @@ static int fetch_with_import(struct transport *transport,
sendline(data, &buf);
strbuf_reset(&buf);
}
disconnect_helper(transport);
finish_command(&fastimport);
write_constant(data->helper->in, "\n");
if (finish_command(&fastimport))
die("Error while running fast-import");
free(fastimport.argv);
fastimport.argv = NULL;
@ -554,6 +576,88 @@ static int fetch(struct transport *transport,
return -1;
}
static void push_update_ref_status(struct strbuf *buf,
struct ref **ref,
struct ref *remote_refs)
{
char *refname, *msg;
int status;
if (!prefixcmp(buf->buf, "ok ")) {
status = REF_STATUS_OK;
refname = buf->buf + 3;
} else if (!prefixcmp(buf->buf, "error ")) {
status = REF_STATUS_REMOTE_REJECT;
refname = buf->buf + 6;
} else
die("expected ok/error, helper said '%s'\n", buf->buf);
msg = strchr(refname, ' ');
if (msg) {
struct strbuf msg_buf = STRBUF_INIT;
const char *end;
*msg++ = '\0';
if (!unquote_c_style(&msg_buf, msg, &end))
msg = strbuf_detach(&msg_buf, NULL);
else
msg = xstrdup(msg);
strbuf_release(&msg_buf);
if (!strcmp(msg, "no match")) {
status = REF_STATUS_NONE;
free(msg);
msg = NULL;
}
else if (!strcmp(msg, "up to date")) {
status = REF_STATUS_UPTODATE;
free(msg);
msg = NULL;
}
else if (!strcmp(msg, "non-fast forward")) {
status = REF_STATUS_REJECT_NONFASTFORWARD;
free(msg);
msg = NULL;
}
}
if (*ref)
*ref = find_ref_by_name(*ref, refname);
if (!*ref)
*ref = find_ref_by_name(remote_refs, refname);
if (!*ref) {
warning("helper reported unexpected status of %s", refname);
return;
}
if ((*ref)->status != REF_STATUS_NONE) {
/*
* Earlier, the ref was marked not to be pushed, so ignore the ref
* status reported by the remote helper if the latter is 'no match'.
*/
if (status == REF_STATUS_NONE)
return;
}
(*ref)->status = status;
(*ref)->remote_status = msg;
}
static void push_update_refs_status(struct helper_data *data,
struct ref *remote_refs)
{
struct strbuf buf = STRBUF_INIT;
struct ref *ref = remote_refs;
for (;;) {
recvline(data, &buf);
if (!buf.len)
break;
push_update_ref_status(&buf, &ref, remote_refs);
}
strbuf_release(&buf);
}
static int push_refs_with_push(struct transport *transport,
struct ref *remote_refs, int flags)
{
@ -608,76 +712,9 @@ static int push_refs_with_push(struct transport *transport,
strbuf_addch(&buf, '\n');
sendline(data, &buf);
ref = remote_refs;
while (1) {
char *refname, *msg;
int status;
recvline(data, &buf);
if (!buf.len)
break;
if (!prefixcmp(buf.buf, "ok ")) {
status = REF_STATUS_OK;
refname = buf.buf + 3;
} else if (!prefixcmp(buf.buf, "error ")) {
status = REF_STATUS_REMOTE_REJECT;
refname = buf.buf + 6;
} else
die("expected ok/error, helper said '%s'\n", buf.buf);
msg = strchr(refname, ' ');
if (msg) {
struct strbuf msg_buf = STRBUF_INIT;
const char *end;
*msg++ = '\0';
if (!unquote_c_style(&msg_buf, msg, &end))
msg = strbuf_detach(&msg_buf, NULL);
else
msg = xstrdup(msg);
strbuf_release(&msg_buf);
if (!strcmp(msg, "no match")) {
status = REF_STATUS_NONE;
free(msg);
msg = NULL;
}
else if (!strcmp(msg, "up to date")) {
status = REF_STATUS_UPTODATE;
free(msg);
msg = NULL;
}
else if (!strcmp(msg, "non-fast forward")) {
status = REF_STATUS_REJECT_NONFASTFORWARD;
free(msg);
msg = NULL;
}
}
if (ref)
ref = find_ref_by_name(ref, refname);
if (!ref)
ref = find_ref_by_name(remote_refs, refname);
if (!ref) {
warning("helper reported unexpected status of %s", refname);
continue;
}
if (ref->status != REF_STATUS_NONE) {
/*
* Earlier, the ref was marked not to be pushed, so ignore the ref
* status reported by the remote helper if the latter is 'no match'.
*/
if (status == REF_STATUS_NONE)
continue;
}
ref->status = status;
ref->remote_status = msg;
}
strbuf_release(&buf);
push_update_refs_status(data, remote_refs);
return 0;
}
@ -687,7 +724,6 @@ static int push_refs_with_export(struct transport *transport,
struct ref *ref;
struct child_process *helper, exporter;
struct helper_data *data = transport->data;
char *export_marks = NULL, *import_marks = NULL;
struct string_list revlist_args = STRING_LIST_INIT_NODUP;
struct strbuf buf = STRBUF_INIT;
@ -695,26 +731,6 @@ static int push_refs_with_export(struct transport *transport,
write_constant(helper->in, "export\n");
recvline(data, &buf);
if (debug)
fprintf(stderr, "Debug: Got export_marks '%s'\n", buf.buf);
if (buf.len) {
struct strbuf arg = STRBUF_INIT;
strbuf_addstr(&arg, "--export-marks=");
strbuf_addbuf(&arg, &buf);
export_marks = strbuf_detach(&arg, NULL);
}
recvline(data, &buf);
if (debug)
fprintf(stderr, "Debug: Got import_marks '%s'\n", buf.buf);
if (buf.len) {
struct strbuf arg = STRBUF_INIT;
strbuf_addstr(&arg, "--import-marks=");
strbuf_addbuf(&arg, &buf);
import_marks = strbuf_detach(&arg, NULL);
}
strbuf_reset(&buf);
for (ref = remote_refs; ref; ref = ref->next) {
@ -728,18 +744,23 @@ static int push_refs_with_export(struct transport *transport,
strbuf_addf(&buf, "^%s", private);
string_list_append(&revlist_args, strbuf_detach(&buf, NULL));
}
free(private);
string_list_append(&revlist_args, ref->name);
if (ref->deletion) {
die("remote-helpers do not support ref deletion");
}
if (ref->peer_ref)
string_list_append(&revlist_args, ref->peer_ref->name);
}
if (get_exporter(transport, &exporter,
export_marks, import_marks, &revlist_args))
if (get_exporter(transport, &exporter, &revlist_args))
die("Couldn't run fast-export");
data->no_disconnect_req = 1;
finish_command(&exporter);
disconnect_helper(transport);
if (finish_command(&exporter))
die("Error while running fast-export");
push_update_refs_status(data, remote_refs);
return 0;
}