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 ...maint
commit
59d9ba869e
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 @@ COMMANDS = {
|
|||
'list': do_list,
|
||||
'import': do_import,
|
||||
'export': do_export,
|
||||
'gitdir': do_gitdir,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@ import os
|
|||
import subprocess
|
||||
import sys
|
||||
|
||||
from git_remote_helpers.util import check_call
|
||||
|
||||
|
||||
class GitExporter(object):
|
||||
"""An exporter for testgit repositories.
|
||||
|
@ -15,7 +17,7 @@ class GitExporter(object):
|
|||
|
||||
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 @@ class GitExporter(object):
|
|||
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 @@ class GitExporter(object):
|
|||
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)
|
||||
|
|
|
@ -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 @@ class GitImporter(object):
|
|||
|
||||
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 @@ class GitImporter(object):
|
|||
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
|
||||
|
|
|
@ -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 @@ class NonLocalGit(object):
|
|||
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 @@ class NonLocalGit(object):
|
|||
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 @@ class NonLocalGit(object):
|
|||
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)
|
||||
|
|
|
@ -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 @@ class GitRepo(object):
|
|||
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:
|
||||
|
|
|
@ -11,6 +11,21 @@ import sys
|
|||
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.
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue