cli.py 29 KB

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