cli.py 27 KB

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