cli.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823
  1. #!/usr/bin/python3 -u
  2. #
  3. # dulwich - Simple command-line interface to Dulwich
  4. # Copyright (C) 2008-2011 Jelmer Vernooij <jelmer@jelmer.uk>
  5. # vim: expandtab
  6. #
  7. # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
  8. # General Public License as public by the Free Software Foundation; version 2.0
  9. # or (at your option) any later version. You can redistribute it and/or
  10. # modify it under the terms of either of these two licenses.
  11. #
  12. # Unless required by applicable law or agreed to in writing, software
  13. # distributed under the License is distributed on an "AS IS" BASIS,
  14. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. # See the License for the specific language governing permissions and
  16. # limitations under the License.
  17. #
  18. # You should have received a copy of the licenses; if not, see
  19. # <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
  20. # and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
  21. # License, Version 2.0.
  22. #
  23. """Simple command-line interface to Dulwich>
  24. This is a very simple command-line wrapper for Dulwich. It is by
  25. no means intended to be a full-blown Git command-line interface but just
  26. a way to test Dulwich.
  27. """
  28. import os
  29. import sys
  30. from getopt import getopt
  31. import argparse
  32. import optparse
  33. import signal
  34. from typing import Dict, Type, Optional
  35. from dulwich import porcelain
  36. from dulwich.client import get_transport_and_path, GitProtocolError
  37. from dulwich.errors import ApplyDeltaError
  38. from dulwich.index import Index
  39. from dulwich.objectspec import parse_commit
  40. from dulwich.pack import Pack, sha_to_hex
  41. from dulwich.repo import Repo
  42. def signal_int(signal, frame):
  43. sys.exit(1)
  44. def signal_quit(signal, frame):
  45. import pdb
  46. pdb.set_trace()
  47. class Command:
  48. """A Dulwich subcommand."""
  49. def run(self, args):
  50. """Run the command."""
  51. raise NotImplementedError(self.run)
  52. class cmd_archive(Command):
  53. def run(self, args):
  54. parser = argparse.ArgumentParser()
  55. parser.add_argument(
  56. "--remote",
  57. type=str,
  58. help="Retrieve archive from specified remote repo",
  59. )
  60. parser.add_argument('committish', type=str, nargs='?')
  61. args = parser.parse_args(args)
  62. if args.remote:
  63. client, path = get_transport_and_path(args.remote)
  64. client.archive(
  65. path,
  66. args.committish,
  67. sys.stdout.write,
  68. write_error=sys.stderr.write,
  69. )
  70. else:
  71. porcelain.archive(
  72. ".", args.committish, outstream=sys.stdout.buffer,
  73. errstream=sys.stderr
  74. )
  75. class cmd_add(Command):
  76. def run(self, argv):
  77. parser = argparse.ArgumentParser()
  78. args = parser.parse_args(argv)
  79. porcelain.add(".", paths=args)
  80. class cmd_rm(Command):
  81. def run(self, argv):
  82. parser = argparse.ArgumentParser()
  83. args = parser.parse_args(argv)
  84. porcelain.rm(".", paths=args)
  85. class cmd_fetch_pack(Command):
  86. def run(self, argv):
  87. parser = argparse.ArgumentParser()
  88. parser.add_argument('--all', action='store_true')
  89. parser.add_argument('location', nargs='?', type=str)
  90. args = parser.parse_args(argv)
  91. client, path = get_transport_and_path(args.location)
  92. r = Repo(".")
  93. if args.all:
  94. determine_wants = r.object_store.determine_wants_all
  95. else:
  96. def determine_wants(x, **kwargs):
  97. return [y for y in args if y not in r.object_store]
  98. client.fetch(path, r, determine_wants)
  99. class cmd_fetch(Command):
  100. def run(self, args):
  101. opts, args = getopt(args, "", [])
  102. opts = dict(opts)
  103. client, path = get_transport_and_path(args.pop(0))
  104. r = Repo(".")
  105. refs = client.fetch(path, r, progress=sys.stdout.write)
  106. print("Remote refs:")
  107. for item in refs.items():
  108. print("%s -> %s" % item)
  109. class cmd_fsck(Command):
  110. def run(self, args):
  111. opts, args = getopt(args, "", [])
  112. opts = dict(opts)
  113. for (obj, msg) in porcelain.fsck("."):
  114. print("{}: {}".format(obj, msg))
  115. class cmd_log(Command):
  116. def run(self, args):
  117. parser = optparse.OptionParser()
  118. parser.add_option(
  119. "--reverse",
  120. dest="reverse",
  121. action="store_true",
  122. help="Reverse order in which entries are printed",
  123. )
  124. parser.add_option(
  125. "--name-status",
  126. dest="name_status",
  127. action="store_true",
  128. help="Print name/status for each changed file",
  129. )
  130. options, args = parser.parse_args(args)
  131. porcelain.log(
  132. ".",
  133. paths=args,
  134. reverse=options.reverse,
  135. name_status=options.name_status,
  136. outstream=sys.stdout,
  137. )
  138. class cmd_diff(Command):
  139. def run(self, args):
  140. opts, args = getopt(args, "", [])
  141. r = Repo(".")
  142. if args == []:
  143. commit_id = b'HEAD'
  144. else:
  145. commit_id = args[0]
  146. commit = parse_commit(r, commit_id)
  147. parent_commit = r[commit.parents[0]]
  148. porcelain.diff_tree(
  149. r, parent_commit.tree, commit.tree, outstream=sys.stdout.buffer)
  150. class cmd_dump_pack(Command):
  151. def run(self, args):
  152. opts, args = getopt(args, "", [])
  153. if args == []:
  154. print("Usage: dulwich dump-pack FILENAME")
  155. sys.exit(1)
  156. basename, _ = os.path.splitext(args[0])
  157. x = Pack(basename)
  158. print("Object names checksum: %s" % x.name())
  159. print("Checksum: %s" % sha_to_hex(x.get_stored_checksum()))
  160. if not x.check():
  161. print("CHECKSUM DOES NOT MATCH")
  162. print("Length: %d" % len(x))
  163. for name in x:
  164. try:
  165. print("\t%s" % x[name])
  166. except KeyError as k:
  167. print("\t{}: Unable to resolve base {}".format(name, k))
  168. except ApplyDeltaError as e:
  169. print("\t{}: Unable to apply delta: {!r}".format(name, e))
  170. class cmd_dump_index(Command):
  171. def run(self, args):
  172. opts, args = getopt(args, "", [])
  173. if args == []:
  174. print("Usage: dulwich dump-index FILENAME")
  175. sys.exit(1)
  176. filename = args[0]
  177. idx = Index(filename)
  178. for o in idx:
  179. print(o, idx[o])
  180. class cmd_init(Command):
  181. def run(self, args):
  182. opts, args = getopt(args, "", ["bare"])
  183. opts = dict(opts)
  184. if args == []:
  185. path = os.getcwd()
  186. else:
  187. path = args[0]
  188. porcelain.init(path, bare=("--bare" in opts))
  189. class cmd_clone(Command):
  190. def run(self, args):
  191. parser = optparse.OptionParser()
  192. parser.add_option(
  193. "--bare",
  194. dest="bare",
  195. help="Whether to create a bare repository.",
  196. action="store_true",
  197. )
  198. parser.add_option(
  199. "--depth", dest="depth", type=int, help="Depth at which to fetch"
  200. )
  201. parser.add_option(
  202. "-b", "--branch", dest="branch", type=str,
  203. help=("Check out branch instead of branch pointed to by remote "
  204. "HEAD"))
  205. options, args = parser.parse_args(args)
  206. if args == []:
  207. print("usage: dulwich clone host:path [PATH]")
  208. sys.exit(1)
  209. source = args.pop(0)
  210. if len(args) > 0:
  211. target = args.pop(0)
  212. else:
  213. target = None
  214. try:
  215. porcelain.clone(source, target, bare=options.bare, depth=options.depth,
  216. branch=options.branch)
  217. except GitProtocolError as e:
  218. print("%s" % e)
  219. class cmd_commit(Command):
  220. def run(self, args):
  221. opts, args = getopt(args, "", ["message"])
  222. opts = dict(opts)
  223. porcelain.commit(".", message=opts["--message"])
  224. class cmd_commit_tree(Command):
  225. def run(self, args):
  226. opts, args = getopt(args, "", ["message"])
  227. if args == []:
  228. print("usage: dulwich commit-tree tree")
  229. sys.exit(1)
  230. opts = dict(opts)
  231. porcelain.commit_tree(".", tree=args[0], message=opts["--message"])
  232. class cmd_update_server_info(Command):
  233. def run(self, args):
  234. porcelain.update_server_info(".")
  235. class cmd_symbolic_ref(Command):
  236. def run(self, args):
  237. opts, args = getopt(args, "", ["ref-name", "force"])
  238. if not args:
  239. print("Usage: dulwich symbolic-ref REF_NAME [--force]")
  240. sys.exit(1)
  241. ref_name = args.pop(0)
  242. porcelain.symbolic_ref(".", ref_name=ref_name, force="--force" in args)
  243. class cmd_pack_refs(Command):
  244. def run(self, argv):
  245. parser = argparse.ArgumentParser()
  246. parser.add_argument('--all', action='store_true')
  247. # ignored, we never prune
  248. parser.add_argument('--no-prune', action='store_true')
  249. args = parser.parse_args(argv)
  250. porcelain.pack_refs(".", all=args.all)
  251. class cmd_show(Command):
  252. def run(self, argv):
  253. parser = argparse.ArgumentParser()
  254. parser.add_argument('objectish', type=str, nargs='*')
  255. args = parser.parse_args(argv)
  256. porcelain.show(".", args.objectish or None)
  257. class cmd_diff_tree(Command):
  258. def run(self, args):
  259. opts, args = getopt(args, "", [])
  260. if len(args) < 2:
  261. print("Usage: dulwich diff-tree OLD-TREE NEW-TREE")
  262. sys.exit(1)
  263. porcelain.diff_tree(".", args[0], args[1])
  264. class cmd_rev_list(Command):
  265. def run(self, args):
  266. opts, args = getopt(args, "", [])
  267. if len(args) < 1:
  268. print("Usage: dulwich rev-list COMMITID...")
  269. sys.exit(1)
  270. porcelain.rev_list(".", args)
  271. class cmd_tag(Command):
  272. def run(self, args):
  273. parser = optparse.OptionParser()
  274. parser.add_option(
  275. "-a",
  276. "--annotated",
  277. help="Create an annotated tag.",
  278. action="store_true",
  279. )
  280. parser.add_option(
  281. "-s", "--sign", help="Sign the annotated tag.", action="store_true"
  282. )
  283. options, args = parser.parse_args(args)
  284. porcelain.tag_create(
  285. ".", args[0], annotated=options.annotated, sign=options.sign
  286. )
  287. class cmd_repack(Command):
  288. def run(self, args):
  289. opts, args = getopt(args, "", [])
  290. opts = dict(opts)
  291. porcelain.repack(".")
  292. class cmd_reset(Command):
  293. def run(self, args):
  294. opts, args = getopt(args, "", ["hard", "soft", "mixed"])
  295. opts = dict(opts)
  296. mode = ""
  297. if "--hard" in opts:
  298. mode = "hard"
  299. elif "--soft" in opts:
  300. mode = "soft"
  301. elif "--mixed" in opts:
  302. mode = "mixed"
  303. porcelain.reset(".", mode=mode, *args)
  304. class cmd_daemon(Command):
  305. def run(self, args):
  306. from dulwich import log_utils
  307. from dulwich.protocol import TCP_GIT_PORT
  308. parser = optparse.OptionParser()
  309. parser.add_option(
  310. "-l",
  311. "--listen_address",
  312. dest="listen_address",
  313. default="localhost",
  314. help="Binding IP address.",
  315. )
  316. parser.add_option(
  317. "-p",
  318. "--port",
  319. dest="port",
  320. type=int,
  321. default=TCP_GIT_PORT,
  322. help="Binding TCP port.",
  323. )
  324. options, args = parser.parse_args(args)
  325. log_utils.default_logging_config()
  326. if len(args) >= 1:
  327. gitdir = args[0]
  328. else:
  329. gitdir = "."
  330. porcelain.daemon(gitdir, address=options.listen_address, port=options.port)
  331. class cmd_web_daemon(Command):
  332. def run(self, args):
  333. from dulwich import log_utils
  334. parser = optparse.OptionParser()
  335. parser.add_option(
  336. "-l",
  337. "--listen_address",
  338. dest="listen_address",
  339. default="",
  340. help="Binding IP address.",
  341. )
  342. parser.add_option(
  343. "-p",
  344. "--port",
  345. dest="port",
  346. type=int,
  347. default=8000,
  348. help="Binding TCP port.",
  349. )
  350. options, args = parser.parse_args(args)
  351. log_utils.default_logging_config()
  352. if len(args) >= 1:
  353. gitdir = args[0]
  354. else:
  355. gitdir = "."
  356. porcelain.web_daemon(gitdir, address=options.listen_address, port=options.port)
  357. class cmd_write_tree(Command):
  358. def run(self, args):
  359. parser = optparse.OptionParser()
  360. options, args = parser.parse_args(args)
  361. sys.stdout.write("%s\n" % porcelain.write_tree("."))
  362. class cmd_receive_pack(Command):
  363. def run(self, args):
  364. parser = optparse.OptionParser()
  365. options, args = parser.parse_args(args)
  366. if len(args) >= 1:
  367. gitdir = args[0]
  368. else:
  369. gitdir = "."
  370. porcelain.receive_pack(gitdir)
  371. class cmd_upload_pack(Command):
  372. def run(self, args):
  373. parser = optparse.OptionParser()
  374. options, args = parser.parse_args(args)
  375. if len(args) >= 1:
  376. gitdir = args[0]
  377. else:
  378. gitdir = "."
  379. porcelain.upload_pack(gitdir)
  380. class cmd_status(Command):
  381. def run(self, args):
  382. parser = optparse.OptionParser()
  383. options, args = parser.parse_args(args)
  384. if len(args) >= 1:
  385. gitdir = args[0]
  386. else:
  387. gitdir = "."
  388. status = porcelain.status(gitdir)
  389. if any(names for (kind, names) in status.staged.items()):
  390. sys.stdout.write("Changes to be committed:\n\n")
  391. for kind, names in status.staged.items():
  392. for name in names:
  393. sys.stdout.write(
  394. "\t{}: {}\n".format(kind, name.decode(sys.getfilesystemencoding()))
  395. )
  396. sys.stdout.write("\n")
  397. if status.unstaged:
  398. sys.stdout.write("Changes not staged for commit:\n\n")
  399. for name in status.unstaged:
  400. sys.stdout.write("\t%s\n" % name.decode(sys.getfilesystemencoding()))
  401. sys.stdout.write("\n")
  402. if status.untracked:
  403. sys.stdout.write("Untracked files:\n\n")
  404. for name in status.untracked:
  405. sys.stdout.write("\t%s\n" % name)
  406. sys.stdout.write("\n")
  407. class cmd_ls_remote(Command):
  408. def run(self, args):
  409. opts, args = getopt(args, "", [])
  410. if len(args) < 1:
  411. print("Usage: dulwich ls-remote URL")
  412. sys.exit(1)
  413. refs = porcelain.ls_remote(args[0])
  414. for ref in sorted(refs):
  415. sys.stdout.write("{}\t{}\n".format(ref, refs[ref]))
  416. class cmd_ls_tree(Command):
  417. def run(self, args):
  418. parser = optparse.OptionParser()
  419. parser.add_option(
  420. "-r",
  421. "--recursive",
  422. action="store_true",
  423. help="Recursively list tree contents.",
  424. )
  425. parser.add_option("--name-only", action="store_true", help="Only display name.")
  426. options, args = parser.parse_args(args)
  427. try:
  428. treeish = args.pop(0)
  429. except IndexError:
  430. treeish = None
  431. porcelain.ls_tree(
  432. ".",
  433. treeish,
  434. outstream=sys.stdout,
  435. recursive=options.recursive,
  436. name_only=options.name_only,
  437. )
  438. class cmd_pack_objects(Command):
  439. def run(self, args):
  440. deltify = False
  441. reuse_deltas = True
  442. opts, args = getopt(args, "", ["stdout", "deltify", "no-reuse-deltas"])
  443. opts = dict(opts)
  444. if len(args) < 1 and "--stdout" not in opts.keys():
  445. print("Usage: dulwich pack-objects basename")
  446. sys.exit(1)
  447. object_ids = [line.strip() for line in sys.stdin.readlines()]
  448. if "--deltify" in opts.keys():
  449. deltify = True
  450. if "--no-reuse-deltas" in opts.keys():
  451. reuse_deltas = False
  452. if "--stdout" in opts.keys():
  453. packf = getattr(sys.stdout, "buffer", sys.stdout)
  454. idxf = None
  455. close = []
  456. else:
  457. basename = args[0]
  458. packf = open(basename + ".pack", "wb")
  459. idxf = open(basename + ".idx", "wb")
  460. close = [packf, idxf]
  461. porcelain.pack_objects(
  462. ".",
  463. object_ids,
  464. packf,
  465. idxf,
  466. deltify=deltify,
  467. reuse_deltas=reuse_deltas)
  468. for f in close:
  469. f.close()
  470. class cmd_pull(Command):
  471. def run(self, args):
  472. parser = optparse.OptionParser()
  473. options, args = parser.parse_args(args)
  474. try:
  475. from_location = args[0]
  476. except IndexError:
  477. from_location = None
  478. porcelain.pull(".", from_location)
  479. class cmd_push(Command):
  480. def run(self, argv):
  481. parser = argparse.ArgumentParser()
  482. parser.add_argument('-f', '--force', action='store_true', help='Force')
  483. parser.add_argument('to_location', type=str)
  484. parser.add_argument('refspec', type=str, nargs='*')
  485. args = parser.parse_args(argv)
  486. try:
  487. porcelain.push('.', args.to_location, args.refspec or None, force=args.force)
  488. except porcelain.DivergedBranches:
  489. sys.stderr.write('Diverged branches; specify --force to override')
  490. return 1
  491. class cmd_remote_add(Command):
  492. def run(self, args):
  493. parser = optparse.OptionParser()
  494. options, args = parser.parse_args(args)
  495. porcelain.remote_add(".", args[0], args[1])
  496. class SuperCommand(Command):
  497. subcommands: Dict[str, Type[Command]] = {}
  498. default_command: Optional[Type[Command]] = None
  499. def run(self, args):
  500. if not args and not self.default_command:
  501. print("Supported subcommands: %s" % ", ".join(self.subcommands.keys()))
  502. return False
  503. cmd = args[0]
  504. try:
  505. cmd_kls = self.subcommands[cmd]
  506. except KeyError:
  507. print("No such subcommand: %s" % args[0])
  508. return False
  509. return cmd_kls().run(args[1:])
  510. class cmd_remote(SuperCommand):
  511. subcommands = {
  512. "add": cmd_remote_add,
  513. }
  514. class cmd_submodule_list(Command):
  515. def run(self, argv):
  516. parser = argparse.ArgumentParser()
  517. parser.parse_args(argv)
  518. for path, sha in porcelain.submodule_list("."):
  519. sys.stdout.write(' {} {}\n'.format(sha, path))
  520. class cmd_submodule_init(Command):
  521. def run(self, argv):
  522. parser = argparse.ArgumentParser()
  523. parser.parse_args(argv)
  524. porcelain.submodule_init(".")
  525. class cmd_submodule(SuperCommand):
  526. subcommands = {
  527. "init": cmd_submodule_init,
  528. }
  529. default_command = cmd_submodule_init
  530. class cmd_check_ignore(Command):
  531. def run(self, args):
  532. parser = optparse.OptionParser()
  533. options, args = parser.parse_args(args)
  534. ret = 1
  535. for path in porcelain.check_ignore(".", args):
  536. print(path)
  537. ret = 0
  538. return ret
  539. class cmd_check_mailmap(Command):
  540. def run(self, args):
  541. parser = optparse.OptionParser()
  542. options, args = parser.parse_args(args)
  543. for arg in args:
  544. canonical_identity = porcelain.check_mailmap(".", arg)
  545. print(canonical_identity)
  546. class cmd_stash_list(Command):
  547. def run(self, args):
  548. parser = optparse.OptionParser()
  549. options, args = parser.parse_args(args)
  550. for i, entry in porcelain.stash_list("."):
  551. print("stash@{%d}: %s" % (i, entry.message.rstrip("\n")))
  552. class cmd_stash_push(Command):
  553. def run(self, args):
  554. parser = optparse.OptionParser()
  555. options, args = parser.parse_args(args)
  556. porcelain.stash_push(".")
  557. print("Saved working directory and index state")
  558. class cmd_stash_pop(Command):
  559. def run(self, args):
  560. parser = optparse.OptionParser()
  561. options, args = parser.parse_args(args)
  562. porcelain.stash_pop(".")
  563. print("Restrored working directory and index state")
  564. class cmd_stash(SuperCommand):
  565. subcommands = {
  566. "list": cmd_stash_list,
  567. "pop": cmd_stash_pop,
  568. "push": cmd_stash_push,
  569. }
  570. class cmd_ls_files(Command):
  571. def run(self, args):
  572. parser = optparse.OptionParser()
  573. options, args = parser.parse_args(args)
  574. for name in porcelain.ls_files("."):
  575. print(name)
  576. class cmd_describe(Command):
  577. def run(self, args):
  578. parser = optparse.OptionParser()
  579. options, args = parser.parse_args(args)
  580. print(porcelain.describe("."))
  581. class cmd_help(Command):
  582. def run(self, args):
  583. parser = optparse.OptionParser()
  584. parser.add_option(
  585. "-a",
  586. "--all",
  587. dest="all",
  588. action="store_true",
  589. help="List all commands.",
  590. )
  591. options, args = parser.parse_args(args)
  592. if options.all:
  593. print("Available commands:")
  594. for cmd in sorted(commands):
  595. print(" %s" % cmd)
  596. else:
  597. print(
  598. """\
  599. The dulwich command line tool is currently a very basic frontend for the
  600. Dulwich python module. For full functionality, please see the API reference.
  601. For a list of supported commands, see 'dulwich help -a'.
  602. """
  603. )
  604. commands = {
  605. "add": cmd_add,
  606. "archive": cmd_archive,
  607. "check-ignore": cmd_check_ignore,
  608. "check-mailmap": cmd_check_mailmap,
  609. "clone": cmd_clone,
  610. "commit": cmd_commit,
  611. "commit-tree": cmd_commit_tree,
  612. "describe": cmd_describe,
  613. "daemon": cmd_daemon,
  614. "diff": cmd_diff,
  615. "diff-tree": cmd_diff_tree,
  616. "dump-pack": cmd_dump_pack,
  617. "dump-index": cmd_dump_index,
  618. "fetch-pack": cmd_fetch_pack,
  619. "fetch": cmd_fetch,
  620. "fsck": cmd_fsck,
  621. "help": cmd_help,
  622. "init": cmd_init,
  623. "log": cmd_log,
  624. "ls-files": cmd_ls_files,
  625. "ls-remote": cmd_ls_remote,
  626. "ls-tree": cmd_ls_tree,
  627. "pack-objects": cmd_pack_objects,
  628. "pack-refs": cmd_pack_refs,
  629. "pull": cmd_pull,
  630. "push": cmd_push,
  631. "receive-pack": cmd_receive_pack,
  632. "remote": cmd_remote,
  633. "repack": cmd_repack,
  634. "reset": cmd_reset,
  635. "rev-list": cmd_rev_list,
  636. "rm": cmd_rm,
  637. "show": cmd_show,
  638. "stash": cmd_stash,
  639. "status": cmd_status,
  640. "symbolic-ref": cmd_symbolic_ref,
  641. "submodule": cmd_submodule,
  642. "tag": cmd_tag,
  643. "update-server-info": cmd_update_server_info,
  644. "upload-pack": cmd_upload_pack,
  645. "web-daemon": cmd_web_daemon,
  646. "write-tree": cmd_write_tree,
  647. }
  648. def main(argv=None):
  649. if argv is None:
  650. argv = sys.argv[1:]
  651. if len(argv) < 1:
  652. print("Usage: dulwich <%s> [OPTIONS...]" % ("|".join(commands.keys())))
  653. return 1
  654. cmd = argv[0]
  655. try:
  656. cmd_kls = commands[cmd]
  657. except KeyError:
  658. print("No such subcommand: %s" % cmd)
  659. return 1
  660. # TODO(jelmer): Return non-0 on errors
  661. return cmd_kls().run(argv[1:])
  662. def _main():
  663. if "DULWICH_PDB" in os.environ and getattr(signal, "SIGQUIT", None):
  664. signal.signal(signal.SIGQUIT, signal_quit) # type: ignore
  665. signal.signal(signal.SIGINT, signal_int)
  666. sys.exit(main())
  667. if __name__ == "__main__":
  668. _main()