tddium 1.25.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +15 -0
  2. data/bin/tddium +29 -0
  3. data/lib/tddium.rb +19 -0
  4. data/lib/tddium/agent.rb +3 -0
  5. data/lib/tddium/agent/tddium.rb +122 -0
  6. data/lib/tddium/cli.rb +26 -0
  7. data/lib/tddium/cli/api.rb +319 -0
  8. data/lib/tddium/cli/commands/account.rb +49 -0
  9. data/lib/tddium/cli/commands/activate.rb +14 -0
  10. data/lib/tddium/cli/commands/config.rb +55 -0
  11. data/lib/tddium/cli/commands/describe.rb +96 -0
  12. data/lib/tddium/cli/commands/find_failing.rb +62 -0
  13. data/lib/tddium/cli/commands/github.rb +53 -0
  14. data/lib/tddium/cli/commands/heroku.rb +15 -0
  15. data/lib/tddium/cli/commands/hg.rb +48 -0
  16. data/lib/tddium/cli/commands/keys.rb +83 -0
  17. data/lib/tddium/cli/commands/login.rb +37 -0
  18. data/lib/tddium/cli/commands/logout.rb +14 -0
  19. data/lib/tddium/cli/commands/password.rb +26 -0
  20. data/lib/tddium/cli/commands/rerun.rb +50 -0
  21. data/lib/tddium/cli/commands/server.rb +22 -0
  22. data/lib/tddium/cli/commands/spec.rb +306 -0
  23. data/lib/tddium/cli/commands/status.rb +107 -0
  24. data/lib/tddium/cli/commands/stop.rb +19 -0
  25. data/lib/tddium/cli/commands/suite.rb +110 -0
  26. data/lib/tddium/cli/commands/web.rb +22 -0
  27. data/lib/tddium/cli/config.rb +245 -0
  28. data/lib/tddium/cli/params_helper.rb +36 -0
  29. data/lib/tddium/cli/prompt.rb +128 -0
  30. data/lib/tddium/cli/show.rb +122 -0
  31. data/lib/tddium/cli/suite.rb +179 -0
  32. data/lib/tddium/cli/tddium.rb +153 -0
  33. data/lib/tddium/cli/text_helper.rb +16 -0
  34. data/lib/tddium/cli/timeformat.rb +21 -0
  35. data/lib/tddium/cli/util.rb +132 -0
  36. data/lib/tddium/constant.rb +509 -0
  37. data/lib/tddium/scm.rb +8 -0
  38. data/lib/tddium/scm/git.rb +188 -0
  39. data/lib/tddium/scm/git_log_parser.rb +67 -0
  40. data/lib/tddium/scm/hg.rb +160 -0
  41. data/lib/tddium/scm/hg_log_parser.rb +66 -0
  42. data/lib/tddium/scm/scm.rb +20 -0
  43. data/lib/tddium/script.rb +12 -0
  44. data/lib/tddium/script/git-remote-hg +1258 -0
  45. data/lib/tddium/ssh.rb +66 -0
  46. data/lib/tddium/util.rb +35 -0
  47. data/lib/tddium/version.rb +5 -0
  48. metadata +394 -0
@@ -0,0 +1,20 @@
1
+ # Copyright (c) 2011, 2012, 2013, 2014 Solano Labs All Rights Reserved
2
+
3
+ module Tddium
4
+ class SCM
5
+ def self.configure
6
+ scm = nil
7
+ [::Tddium::Git, ::Tddium::Hg].each do |scm_class|
8
+ sniff_scm = scm_class.new
9
+ if sniff_scm.repo? && scm_class.version_ok
10
+ scm = sniff_scm
11
+ break
12
+ end
13
+ end
14
+
15
+ #default scm is git
16
+ scm = ::Tddium::Git.new unless scm
17
+ scm
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,12 @@
1
+ # Copyright (c) 2014 Solano Labs, Inc. All Rights Reserved
2
+ #
3
+ module Tddium
4
+ class Scripts
5
+ include TddiumConstant
6
+ def self.prepend_script_path
7
+ path = ENV['PATH'].split(':')
8
+ path.unshift(Config::EMBEDDED_SCRIPT_PATH)
9
+ ENV['PATH'] = path.join(':')
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,1258 @@
1
+ #!/usr/bin/env python
2
+ #
3
+ # Copyright (c) 2012 Felipe Contreras
4
+ #
5
+
6
+ # Inspired by Rocco Rutte's hg-fast-export
7
+
8
+ # Just copy to your ~/bin, or anywhere in your $PATH.
9
+ # Then you can clone with:
10
+ # git clone hg::/path/to/mercurial/repo/
11
+ #
12
+ # For remote repositories a local clone is stored in
13
+ # "$GIT_DIR/hg/origin/clone/.hg/".
14
+
15
+ from mercurial import hg, ui, bookmarks, context, encoding, node, error, extensions, discovery, util
16
+
17
+ import re
18
+ import sys
19
+ import os
20
+ import json
21
+ import shutil
22
+ import subprocess
23
+ import urllib
24
+ import atexit
25
+ import urlparse, hashlib
26
+ import time as ptime
27
+
28
+ #
29
+ # If you want to see Mercurial revisions as Git commit notes:
30
+ # git config core.notesRef refs/notes/hg
31
+ #
32
+ # If you are not in hg-git-compat mode and want to disable the tracking of
33
+ # named branches:
34
+ # git config --global remote-hg.track-branches false
35
+ #
36
+ # If you want the equivalent of hg's clone/pull--insecure option:
37
+ # git config --global remote-hg.insecure true
38
+ #
39
+ # If you want to switch to hg-git compatibility mode:
40
+ # git config --global remote-hg.hg-git-compat true
41
+ #
42
+ # git:
43
+ # Sensible defaults for git.
44
+ # hg bookmarks are exported as git branches, hg branches are prefixed
45
+ # with 'branches/', HEAD is a special case.
46
+ #
47
+ # hg:
48
+ # Emulate hg-git.
49
+ # Only hg bookmarks are exported as git branches.
50
+ # Commits are modified to preserve hg information and allow bidirectionality.
51
+ #
52
+
53
+ NAME_RE = re.compile('^([^<>]+)')
54
+ AUTHOR_RE = re.compile('^([^<>]+?)? ?[<>]([^<>]*)(?:$|>)')
55
+ EMAIL_RE = re.compile(r'([^ \t<>]+@[^ \t<>]+)')
56
+ AUTHOR_HG_RE = re.compile('^(.*?) ?<(.*?)(?:>(.+)?)?$')
57
+ RAW_AUTHOR_RE = re.compile('^(\w+) (?:(.+)? )?<(.*)> (\d+) ([+-]\d+)')
58
+
59
+ VERSION = 2
60
+
61
+ def die(msg, *args):
62
+ sys.stderr.write('ERROR: %s\n' % (msg % args))
63
+ sys.exit(1)
64
+
65
+ def warn(msg, *args):
66
+ sys.stderr.write('WARNING: %s\n' % (msg % args))
67
+
68
+ def gitmode(flags):
69
+ return 'l' in flags and '120000' or 'x' in flags and '100755' or '100644'
70
+
71
+ def gittz(tz):
72
+ return '%+03d%02d' % (-tz / 3600, -tz % 3600 / 60)
73
+
74
+ def hgmode(mode):
75
+ m = { '100755': 'x', '120000': 'l' }
76
+ return m.get(mode, '')
77
+
78
+ def hghex(n):
79
+ return node.hex(n)
80
+
81
+ def hgbin(n):
82
+ return node.bin(n)
83
+
84
+ def hgref(ref):
85
+ return ref.replace('___', ' ')
86
+
87
+ def gitref(ref):
88
+ return ref.replace(' ', '___')
89
+
90
+ def check_version(*check):
91
+ if not hg_version:
92
+ return True
93
+ return hg_version >= check
94
+
95
+ def get_config(config):
96
+ cmd = ['git', 'config', '--get', config]
97
+ process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
98
+ output, _ = process.communicate()
99
+ return output
100
+
101
+ def get_config_bool(config, default=False):
102
+ value = get_config(config).rstrip('\n')
103
+ if value == "true":
104
+ return True
105
+ elif value == "false":
106
+ return False
107
+ else:
108
+ return default
109
+
110
+ class Marks:
111
+
112
+ def __init__(self, path, repo):
113
+ self.path = path
114
+ self.repo = repo
115
+ self.clear()
116
+ self.load()
117
+
118
+ if self.version < VERSION:
119
+ if self.version == 1:
120
+ self.upgrade_one()
121
+
122
+ # upgraded?
123
+ if self.version < VERSION:
124
+ self.clear()
125
+ self.version = VERSION
126
+
127
+ def clear(self):
128
+ self.tips = {}
129
+ self.marks = {}
130
+ self.rev_marks = {}
131
+ self.last_mark = 0
132
+ self.version = 0
133
+ self.last_note = 0
134
+
135
+ def load(self):
136
+ if not os.path.exists(self.path):
137
+ return
138
+
139
+ tmp = json.load(open(self.path))
140
+
141
+ self.tips = tmp['tips']
142
+ self.marks = tmp['marks']
143
+ self.last_mark = tmp['last-mark']
144
+ self.version = tmp.get('version', 1)
145
+ self.last_note = tmp.get('last-note', 0)
146
+
147
+ for rev, mark in self.marks.iteritems():
148
+ self.rev_marks[mark] = rev
149
+
150
+ def upgrade_one(self):
151
+ def get_id(rev):
152
+ return hghex(self.repo.changelog.node(int(rev)))
153
+ self.tips = dict((name, get_id(rev)) for name, rev in self.tips.iteritems())
154
+ self.marks = dict((get_id(rev), mark) for rev, mark in self.marks.iteritems())
155
+ self.rev_marks = dict((mark, get_id(rev)) for mark, rev in self.rev_marks.iteritems())
156
+ self.version = 2
157
+
158
+ def dict(self):
159
+ return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark, 'version' : self.version, 'last-note' : self.last_note }
160
+
161
+ def store(self):
162
+ json.dump(self.dict(), open(self.path, 'w'))
163
+
164
+ def __str__(self):
165
+ return str(self.dict())
166
+
167
+ def from_rev(self, rev):
168
+ return self.marks[rev]
169
+
170
+ def to_rev(self, mark):
171
+ return str(self.rev_marks[mark])
172
+
173
+ def next_mark(self):
174
+ self.last_mark += 1
175
+ return self.last_mark
176
+
177
+ def get_mark(self, rev):
178
+ self.last_mark += 1
179
+ self.marks[rev] = self.last_mark
180
+ return self.last_mark
181
+
182
+ def new_mark(self, rev, mark):
183
+ self.marks[rev] = mark
184
+ self.rev_marks[mark] = rev
185
+ self.last_mark = mark
186
+
187
+ def is_marked(self, rev):
188
+ return rev in self.marks
189
+
190
+ def get_tip(self, branch):
191
+ return str(self.tips[branch])
192
+
193
+ def set_tip(self, branch, tip):
194
+ self.tips[branch] = tip
195
+
196
+ class Parser:
197
+
198
+ def __init__(self, repo):
199
+ self.repo = repo
200
+ self.line = self.get_line()
201
+
202
+ def get_line(self):
203
+ return sys.stdin.readline().strip()
204
+
205
+ def __getitem__(self, i):
206
+ return self.line.split()[i]
207
+
208
+ def check(self, word):
209
+ return self.line.startswith(word)
210
+
211
+ def each_block(self, separator):
212
+ while self.line != separator:
213
+ yield self.line
214
+ self.line = self.get_line()
215
+
216
+ def __iter__(self):
217
+ return self.each_block('')
218
+
219
+ def next(self):
220
+ self.line = self.get_line()
221
+ if self.line == 'done':
222
+ self.line = None
223
+
224
+ def get_mark(self):
225
+ i = self.line.index(':') + 1
226
+ return int(self.line[i:])
227
+
228
+ def get_data(self):
229
+ if not self.check('data'):
230
+ return None
231
+ i = self.line.index(' ') + 1
232
+ size = int(self.line[i:])
233
+ return sys.stdin.read(size)
234
+
235
+ def get_author(self):
236
+ ex = None
237
+ m = RAW_AUTHOR_RE.match(self.line)
238
+ if not m:
239
+ return None
240
+ _, name, email, date, tz = m.groups()
241
+ if name and 'ext:' in name:
242
+ m = re.match('^(.+?) ext:\((.+)\)$', name)
243
+ if m:
244
+ name = m.group(1)
245
+ ex = urllib.unquote(m.group(2))
246
+
247
+ if email != bad_mail:
248
+ if name:
249
+ user = '%s <%s>' % (name, email)
250
+ else:
251
+ user = '<%s>' % (email)
252
+ else:
253
+ user = name
254
+
255
+ if ex:
256
+ user += ex
257
+
258
+ tz = int(tz)
259
+ tz = ((tz / 100) * 3600) + ((tz % 100) * 60)
260
+ return (user, int(date), -tz)
261
+
262
+ def fix_file_path(path):
263
+ path = os.path.normpath(path)
264
+ if not os.path.isabs(path):
265
+ return path
266
+ return os.path.relpath(path, '/')
267
+
268
+ def export_files(files):
269
+ final = []
270
+ for f in files:
271
+ fid = node.hex(f.filenode())
272
+
273
+ if fid in filenodes:
274
+ mark = filenodes[fid]
275
+ else:
276
+ mark = marks.next_mark()
277
+ filenodes[fid] = mark
278
+ d = f.data()
279
+
280
+ print "blob"
281
+ print "mark :%u" % mark
282
+ print "data %d" % len(d)
283
+ print d
284
+
285
+ path = fix_file_path(f.path())
286
+ final.append((gitmode(f.flags()), mark, path))
287
+
288
+ return final
289
+
290
+ def get_filechanges(repo, ctx, parent):
291
+ modified = set()
292
+ added = set()
293
+ removed = set()
294
+
295
+ # load earliest manifest first for caching reasons
296
+ prev = parent.manifest().copy()
297
+ cur = ctx.manifest()
298
+
299
+ for fn in cur:
300
+ if fn in prev:
301
+ if (cur.flags(fn) != prev.flags(fn) or cur[fn] != prev[fn]):
302
+ modified.add(fn)
303
+ del prev[fn]
304
+ else:
305
+ added.add(fn)
306
+ removed |= set(prev.keys())
307
+
308
+ return added | modified, removed
309
+
310
+ def fixup_user_git(user):
311
+ name = mail = None
312
+ user = user.replace('"', '')
313
+ m = AUTHOR_RE.match(user)
314
+ if m:
315
+ name = m.group(1)
316
+ mail = m.group(2).strip()
317
+ else:
318
+ m = EMAIL_RE.match(user)
319
+ if m:
320
+ mail = m.group(1)
321
+ else:
322
+ m = NAME_RE.match(user)
323
+ if m:
324
+ name = m.group(1).strip()
325
+ return (name, mail)
326
+
327
+ def fixup_user_hg(user):
328
+ def sanitize(name):
329
+ # stole this from hg-git
330
+ return re.sub('[<>\n]', '?', name.lstrip('< ').rstrip('> '))
331
+
332
+ m = AUTHOR_HG_RE.match(user)
333
+ if m:
334
+ name = sanitize(m.group(1))
335
+ mail = sanitize(m.group(2))
336
+ ex = m.group(3)
337
+ if ex:
338
+ name += ' ext:(' + urllib.quote(ex) + ')'
339
+ else:
340
+ name = sanitize(user)
341
+ if '@' in user:
342
+ mail = name
343
+ else:
344
+ mail = None
345
+
346
+ return (name, mail)
347
+
348
+ def fixup_user(user):
349
+ if mode == 'git':
350
+ name, mail = fixup_user_git(user)
351
+ else:
352
+ name, mail = fixup_user_hg(user)
353
+
354
+ if not name:
355
+ name = bad_name
356
+ if not mail:
357
+ mail = bad_mail
358
+
359
+ return '%s <%s>' % (name, mail)
360
+
361
+ def updatebookmarks(repo, peer):
362
+ remotemarks = peer.listkeys('bookmarks')
363
+ localmarks = repo._bookmarks
364
+
365
+ if not remotemarks:
366
+ return
367
+
368
+ for k, v in remotemarks.iteritems():
369
+ localmarks[k] = hgbin(v)
370
+
371
+ if hasattr(localmarks, 'write'):
372
+ localmarks.write()
373
+ else:
374
+ bookmarks.write(repo)
375
+
376
+ def get_repo(url, alias):
377
+ global peer
378
+
379
+ myui = ui.ui()
380
+ myui.setconfig('ui', 'interactive', 'off')
381
+ myui.fout = sys.stderr
382
+
383
+ if get_config_bool('remote-hg.insecure'):
384
+ myui.setconfig('web', 'cacerts', '')
385
+
386
+ extensions.loadall(myui)
387
+
388
+ if hg.islocal(url) and not os.environ.get('GIT_REMOTE_HG_TEST_REMOTE'):
389
+ repo = hg.repository(myui, url)
390
+ if not os.path.exists(dirname):
391
+ os.makedirs(dirname)
392
+ else:
393
+ shared_path = os.path.join(gitdir, 'hg')
394
+
395
+ # check and upgrade old organization
396
+ hg_path = os.path.join(shared_path, '.hg')
397
+ if os.path.exists(shared_path) and not os.path.exists(hg_path):
398
+ repos = os.listdir(shared_path)
399
+ for x in repos:
400
+ local_hg = os.path.join(shared_path, x, 'clone', '.hg')
401
+ if not os.path.exists(local_hg):
402
+ continue
403
+ if not os.path.exists(hg_path):
404
+ shutil.move(local_hg, hg_path)
405
+ shutil.rmtree(os.path.join(shared_path, x, 'clone'))
406
+
407
+ # setup shared repo (if not there)
408
+ try:
409
+ hg.peer(myui, {}, shared_path, create=True)
410
+ except error.RepoError:
411
+ pass
412
+
413
+ if not os.path.exists(dirname):
414
+ os.makedirs(dirname)
415
+
416
+ local_path = os.path.join(dirname, 'clone')
417
+ if not os.path.exists(local_path):
418
+ hg.share(myui, shared_path, local_path, update=False)
419
+ else:
420
+ # make sure the shared path is always up-to-date
421
+ util.writefile(os.path.join(local_path, '.hg', 'sharedpath'), hg_path)
422
+
423
+ repo = hg.repository(myui, local_path)
424
+ try:
425
+ peer = hg.peer(myui, {}, url)
426
+ except:
427
+ die('Repository error')
428
+ repo.pull(peer, heads=None, force=True)
429
+
430
+ updatebookmarks(repo, peer)
431
+
432
+ return repo
433
+
434
+ def rev_to_mark(rev):
435
+ return marks.from_rev(rev.hex())
436
+
437
+ def mark_to_rev(mark):
438
+ return marks.to_rev(mark)
439
+
440
+ def export_ref(repo, name, kind, head):
441
+ ename = '%s/%s' % (kind, name)
442
+ try:
443
+ tip = marks.get_tip(ename)
444
+ tip = repo[tip].rev()
445
+ except:
446
+ tip = 0
447
+
448
+ revs = xrange(tip, head.rev() + 1)
449
+ total = len(revs)
450
+
451
+ for rev in revs:
452
+
453
+ c = repo[rev]
454
+ node = c.node()
455
+
456
+ if marks.is_marked(c.hex()):
457
+ continue
458
+
459
+ (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(node)
460
+ rev_branch = extra['branch']
461
+
462
+ author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
463
+ if 'committer' in extra:
464
+ user, time, tz = extra['committer'].rsplit(' ', 2)
465
+ committer = "%s %s %s" % (user, time, gittz(int(tz)))
466
+ else:
467
+ committer = author
468
+
469
+ parents = [repo[p] for p in repo.changelog.parentrevs(rev) if p >= 0]
470
+
471
+ if len(parents) == 0:
472
+ modified = c.manifest().keys()
473
+ removed = []
474
+ else:
475
+ modified, removed = get_filechanges(repo, c, parents[0])
476
+
477
+ desc += '\n'
478
+
479
+ if mode == 'hg':
480
+ extra_msg = ''
481
+
482
+ if rev_branch != 'default':
483
+ extra_msg += 'branch : %s\n' % rev_branch
484
+
485
+ renames = []
486
+ for f in c.files():
487
+ if f not in c.manifest():
488
+ continue
489
+ rename = c.filectx(f).renamed()
490
+ if rename:
491
+ renames.append((rename[0], f))
492
+
493
+ for e in renames:
494
+ extra_msg += "rename : %s => %s\n" % e
495
+
496
+ for key, value in extra.iteritems():
497
+ if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
498
+ continue
499
+ else:
500
+ extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
501
+
502
+ if extra_msg:
503
+ desc += '\n--HG--\n' + extra_msg
504
+
505
+ if len(parents) == 0 and rev:
506
+ print 'reset %s/%s' % (prefix, ename)
507
+
508
+ modified_final = export_files(c.filectx(f) for f in modified)
509
+
510
+ print "commit %s/%s" % (prefix, ename)
511
+ print "mark :%d" % (marks.get_mark(c.hex()))
512
+ print "author %s" % (author)
513
+ print "committer %s" % (committer)
514
+ print "data %d" % (len(desc))
515
+ print desc
516
+
517
+ if len(parents) > 0:
518
+ print "from :%s" % (rev_to_mark(parents[0]))
519
+ if len(parents) > 1:
520
+ print "merge :%s" % (rev_to_mark(parents[1]))
521
+
522
+ for f in removed:
523
+ print "D %s" % (fix_file_path(f))
524
+ for f in modified_final:
525
+ print "M %s :%u %s" % f
526
+ print
527
+
528
+ progress = (rev - tip)
529
+ if (progress % 100 == 0):
530
+ print "progress revision %d '%s' (%d/%d)" % (rev, name, progress, total)
531
+
532
+ # make sure the ref is updated
533
+ print "reset %s/%s" % (prefix, ename)
534
+ print "from :%u" % rev_to_mark(head)
535
+ print
536
+
537
+ pending_revs = set(revs) - notes
538
+ if pending_revs:
539
+ note_mark = marks.next_mark()
540
+ ref = "refs/notes/hg"
541
+
542
+ print "commit %s" % ref
543
+ print "mark :%d" % (note_mark)
544
+ print "committer remote-hg <> %d %s" % (ptime.time(), gittz(ptime.timezone))
545
+ desc = "Notes for %s\n" % (name)
546
+ print "data %d" % (len(desc))
547
+ print desc
548
+ if marks.last_note:
549
+ print "from :%u" % marks.last_note
550
+
551
+ for rev in pending_revs:
552
+ notes.add(rev)
553
+ c = repo[rev]
554
+ print "N inline :%u" % rev_to_mark(c)
555
+ msg = c.hex()
556
+ print "data %d" % (len(msg))
557
+ print msg
558
+ print
559
+
560
+ marks.last_note = note_mark
561
+
562
+ marks.set_tip(ename, head.hex())
563
+
564
+ def export_tag(repo, tag):
565
+ export_ref(repo, tag, 'tags', repo[hgref(tag)])
566
+
567
+ def export_bookmark(repo, bmark):
568
+ head = bmarks[hgref(bmark)]
569
+ export_ref(repo, bmark, 'bookmarks', head)
570
+
571
+ def export_branch(repo, branch):
572
+ tip = get_branch_tip(repo, branch)
573
+ head = repo[tip]
574
+ export_ref(repo, branch, 'branches', head)
575
+
576
+ def export_head(repo):
577
+ export_ref(repo, g_head[0], 'bookmarks', g_head[1])
578
+
579
+ def do_capabilities(parser):
580
+ print "import"
581
+ print "export"
582
+ print "refspec refs/heads/branches/*:%s/branches/*" % prefix
583
+ print "refspec refs/heads/*:%s/bookmarks/*" % prefix
584
+ print "refspec refs/tags/*:%s/tags/*" % prefix
585
+
586
+ path = os.path.join(dirname, 'marks-git')
587
+
588
+ if os.path.exists(path):
589
+ print "*import-marks %s" % path
590
+ print "*export-marks %s" % path
591
+ print "option"
592
+
593
+ print
594
+
595
+ def branch_tip(branch):
596
+ return branches[branch][-1]
597
+
598
+ def get_branch_tip(repo, branch):
599
+ heads = branches.get(hgref(branch), None)
600
+ if not heads:
601
+ return None
602
+
603
+ # verify there's only one head
604
+ if (len(heads) > 1):
605
+ warn("Branch '%s' has more than one head, consider merging" % branch)
606
+ return branch_tip(hgref(branch))
607
+
608
+ return heads[0]
609
+
610
+ def list_head(repo, cur):
611
+ global g_head, fake_bmark
612
+
613
+ if 'default' not in branches:
614
+ # empty repo
615
+ return
616
+
617
+ node = repo[branch_tip('default')]
618
+ head = 'master' if not 'master' in bmarks else 'default'
619
+ fake_bmark = head
620
+ bmarks[head] = node
621
+
622
+ head = gitref(head)
623
+ print "@refs/heads/%s HEAD" % head
624
+ g_head = (head, node)
625
+
626
+ def do_list(parser):
627
+ repo = parser.repo
628
+ for bmark, node in bookmarks.listbookmarks(repo).iteritems():
629
+ bmarks[bmark] = repo[node]
630
+
631
+ cur = repo.dirstate.branch()
632
+ orig = peer if peer else repo
633
+
634
+ for branch, heads in orig.branchmap().iteritems():
635
+ # only open heads
636
+ heads = [h for h in heads if 'close' not in repo.changelog.read(h)[5]]
637
+ if heads:
638
+ branches[branch] = heads
639
+
640
+ list_head(repo, cur)
641
+
642
+ if track_branches:
643
+ for branch in branches:
644
+ print "? refs/heads/branches/%s" % gitref(branch)
645
+
646
+ for bmark in bmarks:
647
+ if bmarks[bmark].hex() == '0000000000000000000000000000000000000000':
648
+ warn("Ignoring invalid bookmark '%s'", bmark)
649
+ else:
650
+ print "? refs/heads/%s" % gitref(bmark)
651
+
652
+ for tag, node in repo.tagslist():
653
+ if tag == 'tip':
654
+ continue
655
+ print "? refs/tags/%s" % gitref(tag)
656
+
657
+ print
658
+
659
+ def do_import(parser):
660
+ repo = parser.repo
661
+
662
+ path = os.path.join(dirname, 'marks-git')
663
+
664
+ print "feature done"
665
+ if os.path.exists(path):
666
+ print "feature import-marks=%s" % path
667
+ print "feature export-marks=%s" % path
668
+ print "feature force"
669
+ sys.stdout.flush()
670
+
671
+ tmp = encoding.encoding
672
+ encoding.encoding = 'utf-8'
673
+
674
+ # lets get all the import lines
675
+ while parser.check('import'):
676
+ ref = parser[1]
677
+
678
+ if (ref == 'HEAD'):
679
+ export_head(repo)
680
+ elif ref.startswith('refs/heads/branches/'):
681
+ branch = ref[len('refs/heads/branches/'):]
682
+ export_branch(repo, branch)
683
+ elif ref.startswith('refs/heads/'):
684
+ bmark = ref[len('refs/heads/'):]
685
+ export_bookmark(repo, bmark)
686
+ elif ref.startswith('refs/tags/'):
687
+ tag = ref[len('refs/tags/'):]
688
+ export_tag(repo, tag)
689
+
690
+ parser.next()
691
+
692
+ encoding.encoding = tmp
693
+
694
+ print 'done'
695
+
696
+ def parse_blob(parser):
697
+ parser.next()
698
+ mark = parser.get_mark()
699
+ parser.next()
700
+ data = parser.get_data()
701
+ blob_marks[mark] = data
702
+ parser.next()
703
+
704
+ def get_merge_files(repo, p1, p2, files):
705
+ for e in repo[p1].files():
706
+ if e not in files:
707
+ if e not in repo[p1].manifest():
708
+ continue
709
+ f = { 'ctx' : repo[p1][e] }
710
+ files[e] = f
711
+
712
+ def c_style_unescape(string):
713
+ if string[0] == string[-1] == '"':
714
+ return string.decode('string-escape')[1:-1]
715
+ return string
716
+
717
+ def parse_commit(parser):
718
+ from_mark = merge_mark = None
719
+
720
+ ref = parser[1]
721
+ parser.next()
722
+
723
+ commit_mark = parser.get_mark()
724
+ parser.next()
725
+ author = parser.get_author()
726
+ parser.next()
727
+ committer = parser.get_author()
728
+ parser.next()
729
+ data = parser.get_data()
730
+ parser.next()
731
+ if parser.check('from'):
732
+ from_mark = parser.get_mark()
733
+ parser.next()
734
+ if parser.check('merge'):
735
+ merge_mark = parser.get_mark()
736
+ parser.next()
737
+ if parser.check('merge'):
738
+ die('octopus merges are not supported yet')
739
+
740
+ # fast-export adds an extra newline
741
+ if data[-1] == '\n':
742
+ data = data[:-1]
743
+
744
+ files = {}
745
+
746
+ for line in parser:
747
+ if parser.check('M'):
748
+ t, m, mark_ref, path = line.split(' ', 3)
749
+ mark = int(mark_ref[1:])
750
+ f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
751
+ elif parser.check('D'):
752
+ t, path = line.split(' ', 1)
753
+ f = { 'deleted' : True }
754
+ else:
755
+ die('Unknown file command: %s' % line)
756
+ path = c_style_unescape(path)
757
+ files[path] = f
758
+
759
+ # only export the commits if we are on an internal proxy repo
760
+ if dry_run and not peer:
761
+ parsed_refs[ref] = None
762
+ return
763
+
764
+ def getfilectx(repo, memctx, f):
765
+ of = files[f]
766
+ if 'deleted' in of:
767
+ raise IOError
768
+ if 'ctx' in of:
769
+ return of['ctx']
770
+ is_exec = of['mode'] == 'x'
771
+ is_link = of['mode'] == 'l'
772
+ rename = of.get('rename', None)
773
+ return context.memfilectx(f, of['data'],
774
+ is_link, is_exec, rename)
775
+
776
+ repo = parser.repo
777
+
778
+ user, date, tz = author
779
+ extra = {}
780
+
781
+ if committer != author:
782
+ extra['committer'] = "%s %u %u" % committer
783
+
784
+ if from_mark:
785
+ p1 = mark_to_rev(from_mark)
786
+ else:
787
+ p1 = '0' * 40
788
+
789
+ if merge_mark:
790
+ p2 = mark_to_rev(merge_mark)
791
+ else:
792
+ p2 = '0' * 40
793
+
794
+ #
795
+ # If files changed from any of the parents, hg wants to know, but in git if
796
+ # nothing changed from the first parent, nothing changed.
797
+ #
798
+ if merge_mark:
799
+ get_merge_files(repo, p1, p2, files)
800
+
801
+ # Check if the ref is supposed to be a named branch
802
+ if ref.startswith('refs/heads/branches/'):
803
+ branch = ref[len('refs/heads/branches/'):]
804
+ extra['branch'] = hgref(branch)
805
+
806
+ if mode == 'hg':
807
+ i = data.find('\n--HG--\n')
808
+ if i >= 0:
809
+ tmp = data[i + len('\n--HG--\n'):].strip()
810
+ for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
811
+ if k == 'rename':
812
+ old, new = v.split(' => ', 1)
813
+ files[new]['rename'] = old
814
+ elif k == 'branch':
815
+ extra[k] = v
816
+ elif k == 'extra':
817
+ ek, ev = v.split(' : ', 1)
818
+ extra[ek] = urllib.unquote(ev)
819
+ data = data[:i]
820
+
821
+ ctx = context.memctx(repo, (p1, p2), data,
822
+ files.keys(), getfilectx,
823
+ user, (date, tz), extra)
824
+
825
+ tmp = encoding.encoding
826
+ encoding.encoding = 'utf-8'
827
+
828
+ node = hghex(repo.commitctx(ctx))
829
+
830
+ encoding.encoding = tmp
831
+
832
+ parsed_refs[ref] = node
833
+ marks.new_mark(node, commit_mark)
834
+
835
+ def parse_reset(parser):
836
+ ref = parser[1]
837
+ parser.next()
838
+ # ugh
839
+ if parser.check('commit'):
840
+ parse_commit(parser)
841
+ return
842
+ if not parser.check('from'):
843
+ return
844
+ from_mark = parser.get_mark()
845
+ parser.next()
846
+
847
+ try:
848
+ rev = mark_to_rev(from_mark)
849
+ except KeyError:
850
+ rev = None
851
+ parsed_refs[ref] = rev
852
+
853
+ def parse_tag(parser):
854
+ name = parser[1]
855
+ parser.next()
856
+ from_mark = parser.get_mark()
857
+ parser.next()
858
+ tagger = parser.get_author()
859
+ parser.next()
860
+ data = parser.get_data()
861
+ parser.next()
862
+
863
+ parsed_tags[name] = (tagger, data)
864
+
865
+ def write_tag(repo, tag, node, msg, author):
866
+ branch = repo[node].branch()
867
+ tip = branch_tip(branch)
868
+ tip = repo[tip]
869
+
870
+ def getfilectx(repo, memctx, f):
871
+ try:
872
+ fctx = tip.filectx(f)
873
+ data = fctx.data()
874
+ except error.ManifestLookupError:
875
+ data = ""
876
+ content = data + "%s %s\n" % (node, tag)
877
+ return context.memfilectx(f, content, False, False, None)
878
+
879
+ p1 = tip.hex()
880
+ p2 = '0' * 40
881
+ if author:
882
+ user, date, tz = author
883
+ date_tz = (date, tz)
884
+ else:
885
+ cmd = ['git', 'var', 'GIT_COMMITTER_IDENT']
886
+ process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
887
+ output, _ = process.communicate()
888
+ m = re.match('^.* <.*>', output)
889
+ if m:
890
+ user = m.group(0)
891
+ else:
892
+ user = repo.ui.username()
893
+ date_tz = None
894
+
895
+ ctx = context.memctx(repo, (p1, p2), msg,
896
+ ['.hgtags'], getfilectx,
897
+ user, date_tz, {'branch' : branch})
898
+
899
+ tmp = encoding.encoding
900
+ encoding.encoding = 'utf-8'
901
+
902
+ tagnode = repo.commitctx(ctx)
903
+
904
+ encoding.encoding = tmp
905
+
906
+ return (tagnode, branch)
907
+
908
+ def checkheads_bmark(repo, ref, ctx):
909
+ bmark = ref[len('refs/heads/'):]
910
+ if not bmark in bmarks:
911
+ # new bmark
912
+ return True
913
+
914
+ ctx_old = bmarks[bmark]
915
+ ctx_new = ctx
916
+ if not repo.changelog.descendant(ctx_old.rev(), ctx_new.rev()):
917
+ if force_push:
918
+ print "ok %s forced update" % ref
919
+ else:
920
+ print "error %s non-fast forward" % ref
921
+ return False
922
+
923
+ return True
924
+
925
+ def checkheads(repo, remote, p_revs):
926
+
927
+ remotemap = remote.branchmap()
928
+ if not remotemap:
929
+ # empty repo
930
+ return True
931
+
932
+ new = {}
933
+ ret = True
934
+
935
+ for node, ref in p_revs.iteritems():
936
+ ctx = repo[node]
937
+ branch = ctx.branch()
938
+ if not branch in remotemap:
939
+ # new branch
940
+ continue
941
+ if not ref.startswith('refs/heads/branches'):
942
+ if ref.startswith('refs/heads/'):
943
+ if not checkheads_bmark(repo, ref, ctx):
944
+ ret = False
945
+
946
+ # only check branches
947
+ continue
948
+ new.setdefault(branch, []).append(ctx.rev())
949
+
950
+ for branch, heads in new.iteritems():
951
+ old = [repo.changelog.rev(x) for x in remotemap[branch]]
952
+ for rev in heads:
953
+ if check_version(2, 3):
954
+ ancestors = repo.changelog.ancestors([rev], stoprev=min(old))
955
+ else:
956
+ ancestors = repo.changelog.ancestors(rev)
957
+ found = False
958
+
959
+ for x in old:
960
+ if x in ancestors:
961
+ found = True
962
+ break
963
+
964
+ if found:
965
+ continue
966
+
967
+ node = repo.changelog.node(rev)
968
+ ref = p_revs[node]
969
+ if force_push:
970
+ print "ok %s forced update" % ref
971
+ else:
972
+ print "error %s non-fast forward" % ref
973
+ ret = False
974
+
975
+ return ret
976
+
977
+ def push_unsafe(repo, remote, parsed_refs, p_revs):
978
+
979
+ force = force_push
980
+
981
+ fci = discovery.findcommonincoming
982
+ commoninc = fci(repo, remote, force=force)
983
+ common, _, remoteheads = commoninc
984
+
985
+ if not checkheads(repo, remote, p_revs):
986
+ return None
987
+
988
+ cg = repo.getbundle('push', heads=list(p_revs), common=common)
989
+
990
+ unbundle = remote.capable('unbundle')
991
+ if unbundle:
992
+ if force:
993
+ remoteheads = ['force']
994
+ return remote.unbundle(cg, remoteheads, 'push')
995
+ else:
996
+ return remote.addchangegroup(cg, 'push', repo.url())
997
+
998
+ def push(repo, remote, parsed_refs, p_revs):
999
+ if hasattr(remote, 'canpush') and not remote.canpush():
1000
+ print "error cannot push"
1001
+
1002
+ if not p_revs:
1003
+ # nothing to push
1004
+ return
1005
+
1006
+ lock = None
1007
+ unbundle = remote.capable('unbundle')
1008
+ if not unbundle:
1009
+ lock = remote.lock()
1010
+ try:
1011
+ ret = push_unsafe(repo, remote, parsed_refs, p_revs)
1012
+ finally:
1013
+ if lock is not None:
1014
+ lock.release()
1015
+
1016
+ return ret
1017
+
1018
+ def check_tip(ref, kind, name, heads):
1019
+ try:
1020
+ ename = '%s/%s' % (kind, name)
1021
+ tip = marks.get_tip(ename)
1022
+ except KeyError:
1023
+ return True
1024
+ else:
1025
+ return tip in heads
1026
+
1027
+ def do_export(parser):
1028
+ p_bmarks = []
1029
+ p_revs = {}
1030
+
1031
+ parser.next()
1032
+
1033
+ for line in parser.each_block('done'):
1034
+ if parser.check('blob'):
1035
+ parse_blob(parser)
1036
+ elif parser.check('commit'):
1037
+ parse_commit(parser)
1038
+ elif parser.check('reset'):
1039
+ parse_reset(parser)
1040
+ elif parser.check('tag'):
1041
+ parse_tag(parser)
1042
+ elif parser.check('feature'):
1043
+ pass
1044
+ else:
1045
+ die('unhandled export command: %s' % line)
1046
+
1047
+ need_fetch = False
1048
+
1049
+ for ref, node in parsed_refs.iteritems():
1050
+ bnode = hgbin(node) if node else None
1051
+ if ref.startswith('refs/heads/branches'):
1052
+ branch = ref[len('refs/heads/branches/'):]
1053
+ if branch in branches and bnode in branches[branch]:
1054
+ # up to date
1055
+ continue
1056
+
1057
+ if peer:
1058
+ remotemap = peer.branchmap()
1059
+ if remotemap and branch in remotemap:
1060
+ heads = [hghex(e) for e in remotemap[branch]]
1061
+ if not check_tip(ref, 'branches', branch, heads):
1062
+ print "error %s fetch first" % ref
1063
+ need_fetch = True
1064
+ continue
1065
+
1066
+ p_revs[bnode] = ref
1067
+ print "ok %s" % ref
1068
+ elif ref.startswith('refs/heads/'):
1069
+ bmark = ref[len('refs/heads/'):]
1070
+ new = node
1071
+ old = bmarks[bmark].hex() if bmark in bmarks else ''
1072
+
1073
+ if old == new:
1074
+ continue
1075
+
1076
+ print "ok %s" % ref
1077
+ if bmark != fake_bmark and \
1078
+ not (bmark == 'master' and bmark not in parser.repo._bookmarks):
1079
+ p_bmarks.append((ref, bmark, old, new))
1080
+
1081
+ if peer:
1082
+ remote_old = peer.listkeys('bookmarks').get(bmark)
1083
+ if remote_old:
1084
+ if not check_tip(ref, 'bookmarks', bmark, remote_old):
1085
+ print "error %s fetch first" % ref
1086
+ need_fetch = True
1087
+ continue
1088
+
1089
+ p_revs[bnode] = ref
1090
+ elif ref.startswith('refs/tags/'):
1091
+ if dry_run:
1092
+ print "ok %s" % ref
1093
+ continue
1094
+ tag = ref[len('refs/tags/'):]
1095
+ tag = hgref(tag)
1096
+ author, msg = parsed_tags.get(tag, (None, None))
1097
+ if mode == 'git':
1098
+ if not msg:
1099
+ msg = 'Added tag %s for changeset %s' % (tag, node[:12])
1100
+ tagnode, branch = write_tag(parser.repo, tag, node, msg, author)
1101
+ p_revs[tagnode] = 'refs/heads/branches/' + gitref(branch)
1102
+ else:
1103
+ fp = parser.repo.opener('localtags', 'a')
1104
+ fp.write('%s %s\n' % (node, tag))
1105
+ fp.close()
1106
+ p_revs[bnode] = ref
1107
+ print "ok %s" % ref
1108
+ else:
1109
+ # transport-helper/fast-export bugs
1110
+ continue
1111
+
1112
+ if need_fetch:
1113
+ print
1114
+ return
1115
+
1116
+ if dry_run:
1117
+ if peer and not force_push:
1118
+ checkheads(parser.repo, peer, p_revs)
1119
+ print
1120
+ return
1121
+
1122
+ if peer:
1123
+ if not push(parser.repo, peer, parsed_refs, p_revs):
1124
+ # do not update bookmarks
1125
+ print
1126
+ return
1127
+
1128
+ # update remote bookmarks
1129
+ remote_bmarks = peer.listkeys('bookmarks')
1130
+ for ref, bmark, old, new in p_bmarks:
1131
+ if force_push:
1132
+ old = remote_bmarks.get(bmark, '')
1133
+ if not peer.pushkey('bookmarks', bmark, old, new):
1134
+ print "error %s" % ref
1135
+ else:
1136
+ # update local bookmarks
1137
+ for ref, bmark, old, new in p_bmarks:
1138
+ if not bookmarks.pushbookmark(parser.repo, bmark, old, new):
1139
+ print "error %s" % ref
1140
+
1141
+ print
1142
+
1143
+ def do_option(parser):
1144
+ global dry_run, force_push
1145
+ _, key, value = parser.line.split(' ')
1146
+ if key == 'dry-run':
1147
+ dry_run = (value == 'true')
1148
+ print 'ok'
1149
+ elif key == 'force':
1150
+ force_push = (value == 'true')
1151
+ print 'ok'
1152
+ else:
1153
+ print 'unsupported'
1154
+
1155
+ def fix_path(alias, repo, orig_url):
1156
+ url = urlparse.urlparse(orig_url, 'file')
1157
+ if url.scheme != 'file' or os.path.isabs(os.path.expanduser(url.path)):
1158
+ return
1159
+ abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
1160
+ cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
1161
+ subprocess.call(cmd)
1162
+
1163
+ def main(args):
1164
+ global prefix, gitdir, dirname, branches, bmarks
1165
+ global marks, blob_marks, parsed_refs
1166
+ global peer, mode, bad_mail, bad_name
1167
+ global track_branches, force_push, is_tmp
1168
+ global parsed_tags
1169
+ global filenodes
1170
+ global fake_bmark, hg_version
1171
+ global dry_run
1172
+ global notes, alias
1173
+
1174
+ marks = None
1175
+ is_tmp = False
1176
+ gitdir = os.environ.get('GIT_DIR', None)
1177
+
1178
+ if len(args) < 3:
1179
+ die('Not enough arguments.')
1180
+
1181
+ if not gitdir:
1182
+ die('GIT_DIR not set')
1183
+
1184
+ alias = args[1]
1185
+ url = args[2]
1186
+ peer = None
1187
+
1188
+ hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
1189
+ track_branches = get_config_bool('remote-hg.track-branches', True)
1190
+ force_push = False
1191
+
1192
+ if hg_git_compat:
1193
+ mode = 'hg'
1194
+ bad_mail = 'none@none'
1195
+ bad_name = ''
1196
+ else:
1197
+ mode = 'git'
1198
+ bad_mail = 'unknown'
1199
+ bad_name = 'Unknown'
1200
+
1201
+ if alias[4:] == url:
1202
+ is_tmp = True
1203
+ alias = hashlib.sha1(alias).hexdigest()
1204
+
1205
+ dirname = os.path.join(gitdir, 'hg', alias)
1206
+ branches = {}
1207
+ bmarks = {}
1208
+ blob_marks = {}
1209
+ parsed_refs = {}
1210
+ parsed_tags = {}
1211
+ filenodes = {}
1212
+ fake_bmark = None
1213
+ try:
1214
+ hg_version = tuple(int(e) for e in util.version().split('.'))
1215
+ except:
1216
+ hg_version = None
1217
+ dry_run = False
1218
+ notes = set()
1219
+
1220
+ repo = get_repo(url, alias)
1221
+ prefix = 'refs/hg/%s' % alias
1222
+
1223
+ if not is_tmp:
1224
+ fix_path(alias, peer or repo, url)
1225
+
1226
+ marks_path = os.path.join(dirname, 'marks-hg')
1227
+ marks = Marks(marks_path, repo)
1228
+
1229
+ if sys.platform == 'win32':
1230
+ import msvcrt
1231
+ msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
1232
+
1233
+ parser = Parser(repo)
1234
+ for line in parser:
1235
+ if parser.check('capabilities'):
1236
+ do_capabilities(parser)
1237
+ elif parser.check('list'):
1238
+ do_list(parser)
1239
+ elif parser.check('import'):
1240
+ do_import(parser)
1241
+ elif parser.check('export'):
1242
+ do_export(parser)
1243
+ elif parser.check('option'):
1244
+ do_option(parser)
1245
+ else:
1246
+ die('unhandled command: %s' % line)
1247
+ sys.stdout.flush()
1248
+
1249
+ def bye():
1250
+ if not marks:
1251
+ return
1252
+ if not is_tmp:
1253
+ marks.store()
1254
+ else:
1255
+ shutil.rmtree(dirname)
1256
+
1257
+ atexit.register(bye)
1258
+ sys.exit(main(sys.argv))