Skip to content

Commit 59d9ba8

Browse files
committed
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 ...
2 parents 1df561f + 105fe3e commit 59d9ba8

15 files changed

+475
-187
lines changed

Documentation/git-fast-export.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ marks the same across runs.
8383
allow that. So fake a tagger to be able to fast-import the
8484
output.
8585

86+
--use-done-feature::
87+
Start the stream with a 'feature done' stanza, and terminate
88+
it with a 'done' command.
89+
8690
--no-data::
8791
Skip output of blob objects and instead refer to blobs via
8892
their original SHA-1 hash. This is useful when rewriting the

Documentation/git-fast-import.txt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ OPTIONS
102102
when the `cat-blob` command is encountered in the stream.
103103
The default behaviour is to write to `stdout`.
104104

105+
--done::
106+
Require a `done` command at the end of the stream.
107+
This option might be useful for detecting errors that
108+
cause the frontend to terminate before it has started to
109+
write a stream.
110+
105111
--export-pack-edges=<file>::
106112
After creating a packfile, print a line of data to
107113
<file> listing the filename of the packfile and the last
@@ -331,6 +337,11 @@ and control the current import process. More detailed discussion
331337
standard output. This command is optional and is not needed
332338
to perform an import.
333339

340+
`done`::
341+
Marks the end of the stream. This command is optional
342+
unless the `done` feature was requested using the
343+
`--done` command line option or `feature done` command.
344+
334345
`cat-blob`::
335346
Causes fast-import to print a blob in 'cat-file --batch'
336347
format to the file descriptor set with `--cat-blob-fd` or
@@ -1021,6 +1032,11 @@ notes::
10211032
Versions of fast-import not supporting notes will exit
10221033
with a message indicating so.
10231034

1035+
done::
1036+
Error out if the stream ends without a 'done' command.
1037+
Without this feature, errors causing the frontend to end
1038+
abruptly at a convenient point in the stream can go
1039+
undetected.
10241040

10251041
`option`
10261042
~~~~~~~~
@@ -1050,6 +1066,15 @@ not be passed as option:
10501066
* cat-blob-fd
10511067
* force
10521068

1069+
`done`
1070+
~~~~~~
1071+
If the `done` feature is not in use, treated as if EOF was read.
1072+
This can be used to tell fast-import to finish early.
1073+
1074+
If the `--done` command line option or `feature done` command is
1075+
in use, the `done` command is mandatory and marks the end of the
1076+
stream.
1077+
10531078
Crash Reports
10541079
-------------
10551080
If fast-import is supplied invalid input it will terminate with a

Documentation/git-remote-helpers.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ arguments. The first argument specifies a remote repository as in git;
4848
it is either the name of a configured remote or a URL. The second
4949
argument specifies a URL; it is usually of the form
5050
'<transport>://<address>', but any arbitrary string is possible.
51+
The 'GIT_DIR' environment variable is set up for the remote helper
52+
and can be used to determine where to store additional data or from
53+
which directory to invoke auxiliary git commands.
5154

5255
When git encounters a URL of the form '<transport>://<address>', where
5356
'<transport>' is a protocol that it cannot handle natively, it

builtin/fast-export.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ static int progress;
2626
static enum { ABORT, VERBATIM, WARN, STRIP } signed_tag_mode = ABORT;
2727
static enum { ERROR, DROP, REWRITE } tag_of_filtered_mode = ABORT;
2828
static int fake_missing_tagger;
29+
static int use_done_feature;
2930
static int no_data;
3031
static int full_tree;
3132

@@ -627,6 +628,8 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
627628
"Fake a tagger when tags lack one"),
628629
OPT_BOOLEAN(0, "full-tree", &full_tree,
629630
"Output full tree for each commit"),
631+
OPT_BOOLEAN(0, "use-done-feature", &use_done_feature,
632+
"Use the done feature to terminate the stream"),
630633
{ OPTION_NEGBIT, 0, "data", &no_data, NULL,
631634
"Skip output of blob data",
632635
PARSE_OPT_NOARG | PARSE_OPT_NEGHELP, NULL, 1 },
@@ -648,6 +651,9 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
648651
if (argc > 1)
649652
usage_with_options (fast_export_usage, options);
650653

654+
if (use_done_feature)
655+
printf("feature done\n");
656+
651657
if (import_filename)
652658
import_marks(import_filename);
653659

@@ -675,5 +681,8 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
675681
if (export_filename)
676682
export_marks(export_filename);
677683

684+
if (use_done_feature)
685+
printf("done\n");
686+
678687
return 0;
679688
}

fast-import.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ static unsigned int cmd_save = 100;
355355
static uintmax_t next_mark;
356356
static struct strbuf new_data = STRBUF_INIT;
357357
static int seen_data_command;
358+
static int require_explicit_termination;
358359

359360
/* Signal handling */
360361
static volatile sig_atomic_t checkpoint_requested;
@@ -3140,6 +3141,8 @@ static int parse_one_feature(const char *feature, int from_stream)
31403141
relative_marks_paths = 1;
31413142
} else if (!strcmp(feature, "no-relative-marks")) {
31423143
relative_marks_paths = 0;
3144+
} else if (!strcmp(feature, "done")) {
3145+
require_explicit_termination = 1;
31433146
} else if (!strcmp(feature, "force")) {
31443147
force_update = 1;
31453148
} else if (!strcmp(feature, "notes") || !strcmp(feature, "ls")) {
@@ -3290,6 +3293,8 @@ int main(int argc, const char **argv)
32903293
parse_reset_branch();
32913294
else if (!strcmp("checkpoint", command_buf.buf))
32923295
parse_checkpoint();
3296+
else if (!strcmp("done", command_buf.buf))
3297+
break;
32933298
else if (!prefixcmp(command_buf.buf, "progress "))
32943299
parse_progress();
32953300
else if (!prefixcmp(command_buf.buf, "feature "))
@@ -3309,6 +3314,9 @@ int main(int argc, const char **argv)
33093314
if (!seen_data_command)
33103315
parse_argv();
33113316

3317+
if (require_explicit_termination && feof(stdin))
3318+
die("stream ends early");
3319+
33123320
end_packfile();
33133321

33143322
dump_branches();

git-remote-testgit.py

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def get_repo(alias, url):
3535
prefix = 'refs/testgit/%s/' % alias
3636
debug("prefix: '%s'", prefix)
3737

38-
repo.gitdir = ""
38+
repo.gitdir = os.environ["GIT_DIR"]
3939
repo.alias = alias
4040
repo.prefix = prefix
4141

@@ -70,9 +70,19 @@ def do_capabilities(repo, args):
7070

7171
print "import"
7272
print "export"
73-
print "gitdir"
7473
print "refspec refs/heads/*:%s*" % repo.prefix
7574

75+
dirname = repo.get_base_path(repo.gitdir)
76+
77+
if not os.path.exists(dirname):
78+
os.makedirs(dirname)
79+
80+
path = os.path.join(dirname, 'testgit.marks')
81+
82+
print "*export-marks %s" % path
83+
if os.path.exists(path):
84+
print "*import-marks %s" % path
85+
7686
print # end capabilities
7787

7888

@@ -121,8 +131,24 @@ def do_import(repo, args):
121131
if not repo.gitdir:
122132
die("Need gitdir to import")
123133

134+
ref = args[0]
135+
refs = [ref]
136+
137+
while True:
138+
line = sys.stdin.readline()
139+
if line == '\n':
140+
break
141+
if not line.startswith('import '):
142+
die("Expected import line.")
143+
144+
# strip of leading 'import '
145+
ref = line[7:].strip()
146+
refs.append(ref)
147+
124148
repo = update_local_repo(repo)
125-
repo.exporter.export_repo(repo.gitdir)
149+
repo.exporter.export_repo(repo.gitdir, refs)
150+
151+
print "done"
126152

127153

128154
def do_export(repo, args):
@@ -132,40 +158,22 @@ def do_export(repo, args):
132158
if not repo.gitdir:
133159
die("Need gitdir to export")
134160

135-
dirname = repo.get_base_path(repo.gitdir)
136-
137-
if not os.path.exists(dirname):
138-
os.makedirs(dirname)
139-
140-
path = os.path.join(dirname, 'testgit.marks')
141-
print path
142-
if os.path.exists(path):
143-
print path
144-
else:
145-
print ""
146-
sys.stdout.flush()
147-
148161
update_local_repo(repo)
149-
repo.importer.do_import(repo.gitdir)
150-
repo.non_local.push(repo.gitdir)
151-
152-
153-
def do_gitdir(repo, args):
154-
"""Stores the location of the gitdir.
155-
"""
162+
changed = repo.importer.do_import(repo.gitdir)
156163

157-
if not args:
158-
die("gitdir needs an argument")
164+
if not repo.local:
165+
repo.non_local.push(repo.gitdir)
159166

160-
repo.gitdir = ' '.join(args)
167+
for ref in changed:
168+
print "ok %s" % ref
169+
print
161170

162171

163172
COMMANDS = {
164173
'capabilities': do_capabilities,
165174
'list': do_list,
166175
'import': do_import,
167176
'export': do_export,
168-
'gitdir': do_gitdir,
169177
}
170178

171179

git_remote_helpers/git/exporter.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import subprocess
33
import sys
44

5+
from git_remote_helpers.util import check_call
6+
57

68
class GitExporter(object):
79
"""An exporter for testgit repositories.
@@ -15,16 +17,21 @@ def __init__(self, repo):
1517

1618
self.repo = repo
1719

18-
def export_repo(self, base):
20+
def export_repo(self, base, refs=None):
1921
"""Exports a fast-export stream for the given directory.
2022
2123
Simply delegates to git fast-epxort and pipes it through sed
2224
to make the refs show up under the prefix rather than the
2325
default refs/heads. This is to demonstrate how the export
2426
data can be stored under it's own ref (using the refspec
2527
capability).
28+
29+
If None, refs defaults to ["HEAD"].
2630
"""
2731

32+
if not refs:
33+
refs = ["HEAD"]
34+
2835
dirname = self.repo.get_base_path(base)
2936
path = os.path.abspath(os.path.join(dirname, 'testgit.marks'))
3037

@@ -42,12 +49,10 @@ def export_repo(self, base):
4249
if os.path.exists(path):
4350
args.append("--import-marks=" + path)
4451

45-
args.append("HEAD")
52+
args.extend(refs)
4653

4754
p1 = subprocess.Popen(args, stdout=subprocess.PIPE)
4855

4956
args = ["sed", "s_refs/heads/_" + self.repo.prefix + "_g"]
5057

51-
child = subprocess.Popen(args, stdin=p1.stdout)
52-
if child.wait() != 0:
53-
raise CalledProcessError
58+
check_call(args, stdin=p1.stdout)

git_remote_helpers/git/importer.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import os
22
import subprocess
33

4+
from git_remote_helpers.util import check_call, check_output
5+
46

57
class GitImporter(object):
68
"""An importer for testgit repositories.
@@ -14,6 +16,18 @@ def __init__(self, repo):
1416

1517
self.repo = repo
1618

19+
def get_refs(self, gitdir):
20+
"""Returns a dictionary with refs.
21+
"""
22+
args = ["git", "--git-dir=" + gitdir, "for-each-ref", "refs/heads"]
23+
lines = check_output(args).strip().split('\n')
24+
refs = {}
25+
for line in lines:
26+
value, name = line.split(' ')
27+
name = name.strip('commit\t')
28+
refs[name] = value
29+
return refs
30+
1731
def do_import(self, base):
1832
"""Imports a fast-import stream to the given directory.
1933
@@ -30,11 +44,23 @@ def do_import(self, base):
3044
if not os.path.exists(dirname):
3145
os.makedirs(dirname)
3246

47+
refs_before = self.get_refs(gitdir)
48+
3349
args = ["git", "--git-dir=" + gitdir, "fast-import", "--quiet", "--export-marks=" + path]
3450

3551
if os.path.exists(path):
3652
args.append("--import-marks=" + path)
3753

38-
child = subprocess.Popen(args)
39-
if child.wait() != 0:
40-
raise CalledProcessError
54+
check_call(args)
55+
56+
refs_after = self.get_refs(gitdir)
57+
58+
changed = {}
59+
60+
for name, value in refs_after.iteritems():
61+
if refs_before.get(name) == value:
62+
continue
63+
64+
changed[name] = value
65+
66+
return changed

git_remote_helpers/git/non_local.py

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import os
22
import subprocess
33

4-
from git_remote_helpers.util import die, warn
4+
from git_remote_helpers.util import check_call, die, warn
55

66

77
class NonLocalGit(object):
@@ -29,9 +29,7 @@ def clone(self, base):
2929
os.makedirs(path)
3030
args = ["git", "clone", "--bare", "--quiet", self.repo.gitpath, path]
3131

32-
child = subprocess.Popen(args)
33-
if child.wait() != 0:
34-
raise CalledProcessError
32+
check_call(args)
3533

3634
return path
3735

@@ -45,14 +43,10 @@ def update(self, base):
4543
die("could not find repo at %s", path)
4644

4745
args = ["git", "--git-dir=" + path, "fetch", "--quiet", self.repo.gitpath]
48-
child = subprocess.Popen(args)
49-
if child.wait() != 0:
50-
raise CalledProcessError
46+
check_call(args)
5147

5248
args = ["git", "--git-dir=" + path, "update-ref", "refs/heads/master", "FETCH_HEAD"]
53-
child = subprocess.Popen(args)
54-
if child.wait() != 0:
55-
raise CalledProcessError
49+
child = check_call(args)
5650

5751
def push(self, base):
5852
"""Pushes from the non-local repo to base.
@@ -63,7 +57,5 @@ def push(self, base):
6357
if not os.path.exists(path):
6458
die("could not find repo at %s", path)
6559

66-
args = ["git", "--git-dir=" + path, "push", "--quiet", self.repo.gitpath]
67-
child = subprocess.Popen(args)
68-
if child.wait() != 0:
69-
raise CalledProcessError
60+
args = ["git", "--git-dir=" + path, "push", "--quiet", self.repo.gitpath, "--all"]
61+
child = check_call(args)

0 commit comments

Comments
 (0)