Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/git/remote.py: 50%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

461 statements  

1# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors 

2# 

3# This module is part of GitPython and is released under the 

4# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/ 

5 

6"""Module implementing a remote object allowing easy access to git remotes.""" 

7 

8__all__ = ["RemoteProgress", "PushInfo", "FetchInfo", "Remote"] 

9 

10import contextlib 

11import logging 

12import re 

13 

14from git.cmd import Git, handle_process_output 

15from git.compat import defenc, force_text 

16from git.config import GitConfigParser, SectionConstraint, cp 

17from git.exc import GitCommandError 

18from git.refs import Head, Reference, RemoteReference, SymbolicReference, TagReference 

19from git.util import ( 

20 CallableRemoteProgress, 

21 IterableList, 

22 IterableObj, 

23 LazyMixin, 

24 RemoteProgress, 

25 join_path, 

26) 

27 

28# typing------------------------------------------------------- 

29 

30from typing import ( 

31 Any, 

32 Callable, 

33 Dict, 

34 Iterator, 

35 List, 

36 NoReturn, 

37 Optional, 

38 Sequence, 

39 TYPE_CHECKING, 

40 Type, 

41 Union, 

42 cast, 

43 overload, 

44) 

45 

46from git.types import AnyGitObject, Literal, PathLike 

47 

48if TYPE_CHECKING: 

49 from git.objects.commit import Commit 

50 from git.objects.submodule.base import UpdateProgress 

51 from git.repo.base import Repo 

52 

53flagKeyLiteral = Literal[" ", "!", "+", "-", "*", "=", "t", "?"] 

54 

55# ------------------------------------------------------------- 

56 

57_logger = logging.getLogger(__name__) 

58 

59# { Utilities 

60 

61 

62def add_progress( 

63 kwargs: Any, 

64 git: Git, 

65 progress: Union[RemoteProgress, "UpdateProgress", Callable[..., RemoteProgress], None], 

66) -> Any: 

67 """Add the ``--progress`` flag to the given `kwargs` dict if supported by the git 

68 command. 

69 

70 :note: 

71 If the actual progress in the given progress instance is not given, we do not 

72 request any progress. 

73 

74 :return: 

75 Possibly altered `kwargs` 

76 """ 

77 if progress is not None: 

78 v = git.version_info[:2] 

79 if v >= (1, 7): 

80 kwargs["progress"] = True 

81 # END handle --progress 

82 # END handle progress 

83 return kwargs 

84 

85 

86# } END utilities 

87 

88 

89@overload 

90def to_progress_instance(progress: None) -> RemoteProgress: ... 

91 

92 

93@overload 

94def to_progress_instance(progress: Callable[..., Any]) -> CallableRemoteProgress: ... 

95 

96 

97@overload 

98def to_progress_instance(progress: RemoteProgress) -> RemoteProgress: ... 

99 

100 

101def to_progress_instance( 

102 progress: Union[Callable[..., Any], RemoteProgress, None], 

103) -> Union[RemoteProgress, CallableRemoteProgress]: 

104 """Given the `progress` return a suitable object derived from 

105 :class:`~git.util.RemoteProgress`.""" 

106 # New API only needs progress as a function. 

107 if callable(progress): 

108 return CallableRemoteProgress(progress) 

109 

110 # Where None is passed create a parser that eats the progress. 

111 elif progress is None: 

112 return RemoteProgress() 

113 

114 # Assume its the old API with an instance of RemoteProgress. 

115 return progress 

116 

117 

118class PushInfo(IterableObj): 

119 """ 

120 Carries information about the result of a push operation of a single head:: 

121 

122 info = remote.push()[0] 

123 info.flags # bitflags providing more information about the result 

124 info.local_ref # Reference pointing to the local reference that was pushed 

125 # It is None if the ref was deleted. 

126 info.remote_ref_string # path to the remote reference located on the remote side 

127 info.remote_ref # Remote Reference on the local side corresponding to 

128 # the remote_ref_string. It can be a TagReference as well. 

129 info.old_commit # commit at which the remote_ref was standing before we pushed 

130 # it to local_ref.commit. Will be None if an error was indicated 

131 info.summary # summary line providing human readable english text about the push 

132 """ 

133 

134 __slots__ = ( 

135 "local_ref", 

136 "remote_ref_string", 

137 "flags", 

138 "_old_commit_sha", 

139 "_remote", 

140 "summary", 

141 ) 

142 

143 _id_attribute_ = "pushinfo" 

144 

145 ( 

146 NEW_TAG, 

147 NEW_HEAD, 

148 NO_MATCH, 

149 REJECTED, 

150 REMOTE_REJECTED, 

151 REMOTE_FAILURE, 

152 DELETED, 

153 FORCED_UPDATE, 

154 FAST_FORWARD, 

155 UP_TO_DATE, 

156 ERROR, 

157 ) = [1 << x for x in range(11)] 

158 

159 _flag_map = { 

160 "X": NO_MATCH, 

161 "-": DELETED, 

162 "*": 0, 

163 "+": FORCED_UPDATE, 

164 " ": FAST_FORWARD, 

165 "=": UP_TO_DATE, 

166 "!": ERROR, 

167 } 

168 

169 def __init__( 

170 self, 

171 flags: int, 

172 local_ref: Union[SymbolicReference, None], 

173 remote_ref_string: str, 

174 remote: "Remote", 

175 old_commit: Optional[str] = None, 

176 summary: str = "", 

177 ) -> None: 

178 """Initialize a new instance. 

179 

180 local_ref: HEAD | Head | RemoteReference | TagReference | Reference | SymbolicReference | None 

181 """ 

182 self.flags = flags 

183 self.local_ref = local_ref 

184 self.remote_ref_string = remote_ref_string 

185 self._remote = remote 

186 self._old_commit_sha = old_commit 

187 self.summary = summary 

188 

189 @property 

190 def old_commit(self) -> Union["Commit", None]: 

191 return self._old_commit_sha and self._remote.repo.commit(self._old_commit_sha) or None 

192 

193 @property 

194 def remote_ref(self) -> Union[RemoteReference, TagReference]: 

195 """ 

196 :return: 

197 Remote :class:`~git.refs.reference.Reference` or 

198 :class:`~git.refs.tag.TagReference` in the local repository corresponding to 

199 the :attr:`remote_ref_string` kept in this instance. 

200 """ 

201 # Translate heads to a local remote. Tags stay as they are. 

202 if self.remote_ref_string.startswith("refs/tags"): 

203 return TagReference(self._remote.repo, self.remote_ref_string) 

204 elif self.remote_ref_string.startswith("refs/heads"): 

205 remote_ref = Reference(self._remote.repo, self.remote_ref_string) 

206 return RemoteReference( 

207 self._remote.repo, 

208 "refs/remotes/%s/%s" % (str(self._remote), remote_ref.name), 

209 ) 

210 else: 

211 raise ValueError("Could not handle remote ref: %r" % self.remote_ref_string) 

212 # END 

213 

214 @classmethod 

215 def _from_line(cls, remote: "Remote", line: str) -> "PushInfo": 

216 """Create a new :class:`PushInfo` instance as parsed from line which is expected 

217 to be like refs/heads/master:refs/heads/master 05d2687..1d0568e as bytes.""" 

218 control_character, from_to, summary = line.split("\t", 3) 

219 flags = 0 

220 

221 # Control character handling 

222 try: 

223 flags |= cls._flag_map[control_character] 

224 except KeyError as e: 

225 raise ValueError("Control character %r unknown as parsed from line %r" % (control_character, line)) from e 

226 # END handle control character 

227 

228 # from_to handling 

229 from_ref_string, to_ref_string = from_to.split(":") 

230 if flags & cls.DELETED: 

231 from_ref: Union[SymbolicReference, None] = None 

232 else: 

233 if from_ref_string == "(delete)": 

234 from_ref = None 

235 else: 

236 from_ref = Reference.from_path(remote.repo, from_ref_string) 

237 

238 # Commit handling, could be message or commit info 

239 old_commit: Optional[str] = None 

240 if summary.startswith("["): 

241 if "[rejected]" in summary: 

242 flags |= cls.REJECTED 

243 elif "[remote rejected]" in summary: 

244 flags |= cls.REMOTE_REJECTED 

245 elif "[remote failure]" in summary: 

246 flags |= cls.REMOTE_FAILURE 

247 elif "[no match]" in summary: 

248 flags |= cls.ERROR 

249 elif "[new tag]" in summary: 

250 flags |= cls.NEW_TAG 

251 elif "[new branch]" in summary: 

252 flags |= cls.NEW_HEAD 

253 # `uptodate` encoded in control character 

254 else: 

255 # Fast-forward or forced update - was encoded in control character, 

256 # but we parse the old and new commit. 

257 split_token = "..." 

258 if control_character == " ": 

259 split_token = ".." 

260 old_sha, _new_sha = summary.split(" ")[0].split(split_token) 

261 # Have to use constructor here as the sha usually is abbreviated. 

262 old_commit = old_sha 

263 # END message handling 

264 

265 return PushInfo(flags, from_ref, to_ref_string, remote, old_commit, summary) 

266 

267 @classmethod 

268 def iter_items(cls, repo: "Repo", *args: Any, **kwargs: Any) -> NoReturn: # -> Iterator['PushInfo']: 

269 raise NotImplementedError 

270 

271 

272class PushInfoList(IterableList[PushInfo]): 

273 """:class:`~git.util.IterableList` of :class:`PushInfo` objects.""" 

274 

275 def __new__(cls) -> "PushInfoList": 

276 return cast(PushInfoList, IterableList.__new__(cls, "push_infos")) 

277 

278 def __init__(self) -> None: 

279 super().__init__("push_infos") 

280 self.error: Optional[Exception] = None 

281 

282 def raise_if_error(self) -> None: 

283 """Raise an exception if any ref failed to push.""" 

284 if self.error: 

285 raise self.error 

286 

287 

288class FetchInfo(IterableObj): 

289 """ 

290 Carries information about the results of a fetch operation of a single head:: 

291 

292 info = remote.fetch()[0] 

293 info.ref # Symbolic Reference or RemoteReference to the changed 

294 # remote head or FETCH_HEAD 

295 info.flags # additional flags to be & with enumeration members, 

296 # i.e. info.flags & info.REJECTED 

297 # is 0 if ref is SymbolicReference 

298 info.note # additional notes given by git-fetch intended for the user 

299 info.old_commit # if info.flags & info.FORCED_UPDATE|info.FAST_FORWARD, 

300 # field is set to the previous location of ref, otherwise None 

301 info.remote_ref_path # The path from which we fetched on the remote. It's the remote's version of our info.ref 

302 """ 

303 

304 __slots__ = ("ref", "old_commit", "flags", "note", "remote_ref_path") 

305 

306 _id_attribute_ = "fetchinfo" 

307 

308 ( 

309 NEW_TAG, 

310 NEW_HEAD, 

311 HEAD_UPTODATE, 

312 TAG_UPDATE, 

313 REJECTED, 

314 FORCED_UPDATE, 

315 FAST_FORWARD, 

316 ERROR, 

317 ) = [1 << x for x in range(8)] 

318 

319 _re_fetch_result = re.compile(r"^ *(?:.{0,3})(.) (\[[\w \.$@]+\]|[\w\.$@]+) +(.+) -> ([^ ]+)( \(.*\)?$)?") 

320 

321 _flag_map: Dict[flagKeyLiteral, int] = { 

322 "!": ERROR, 

323 "+": FORCED_UPDATE, 

324 "*": 0, 

325 "=": HEAD_UPTODATE, 

326 " ": FAST_FORWARD, 

327 "-": TAG_UPDATE, 

328 } 

329 

330 @classmethod 

331 def refresh(cls) -> Literal[True]: 

332 """Update information about which :manpage:`git-fetch(1)` flags are supported 

333 by the git executable being used. 

334 

335 Called by the :func:`git.refresh` function in the top level ``__init__``. 

336 """ 

337 # Clear the old values in _flag_map. 

338 with contextlib.suppress(KeyError): 

339 del cls._flag_map["t"] 

340 with contextlib.suppress(KeyError): 

341 del cls._flag_map["-"] 

342 

343 # Set the value given the git version. 

344 if Git().version_info[:2] >= (2, 10): 

345 cls._flag_map["t"] = cls.TAG_UPDATE 

346 else: 

347 cls._flag_map["-"] = cls.TAG_UPDATE 

348 

349 return True 

350 

351 def __init__( 

352 self, 

353 ref: SymbolicReference, 

354 flags: int, 

355 note: str = "", 

356 old_commit: Union[AnyGitObject, None] = None, 

357 remote_ref_path: Optional[PathLike] = None, 

358 ) -> None: 

359 """Initialize a new instance.""" 

360 self.ref = ref 

361 self.flags = flags 

362 self.note = note 

363 self.old_commit = old_commit 

364 self.remote_ref_path = remote_ref_path 

365 

366 def __str__(self) -> str: 

367 return self.name 

368 

369 @property 

370 def name(self) -> str: 

371 """:return: Name of our remote ref""" 

372 return self.ref.name 

373 

374 @property 

375 def commit(self) -> "Commit": 

376 """:return: Commit of our remote ref""" 

377 return self.ref.commit 

378 

379 @classmethod 

380 def _from_line(cls, repo: "Repo", line: str, fetch_line: str) -> "FetchInfo": 

381 """Parse information from the given line as returned by ``git-fetch -v`` and 

382 return a new :class:`FetchInfo` object representing this information. 

383 

384 We can handle a line as follows:: 

385 

386 %c %-*s %-*s -> %s%s 

387 

388 Where ``c`` is either a space, ``!``, ``+``, ``-``, ``*``, or ``=``: 

389 

390 - '!' means error 

391 - '+' means success forcing update 

392 - '-' means a tag was updated 

393 - '*' means birth of new branch or tag 

394 - '=' means the head was up to date (and not moved) 

395 - ' ' means a fast-forward 

396 

397 `fetch_line` is the corresponding line from FETCH_HEAD, like:: 

398 

399 acb0fa8b94ef421ad60c8507b634759a472cd56c not-for-merge branch '0.1.7RC' of /tmp/tmpya0vairemote_repo 

400 """ 

401 match = cls._re_fetch_result.match(line) 

402 if match is None: 

403 raise ValueError("Failed to parse line: %r" % line) 

404 

405 # Parse lines. 

406 remote_local_ref_str: str 

407 ( 

408 control_character, 

409 operation, 

410 local_remote_ref, 

411 remote_local_ref_str, 

412 note, 

413 ) = match.groups() 

414 control_character = cast(flagKeyLiteral, control_character) 

415 try: 

416 _new_hex_sha, _fetch_operation, fetch_note = fetch_line.split("\t") 

417 ref_type_name, fetch_note = fetch_note.split(" ", 1) 

418 except ValueError as e: # unpack error 

419 raise ValueError("Failed to parse FETCH_HEAD line: %r" % fetch_line) from e 

420 

421 # Parse flags from control_character. 

422 flags = 0 

423 try: 

424 flags |= cls._flag_map[control_character] 

425 except KeyError as e: 

426 raise ValueError("Control character %r unknown as parsed from line %r" % (control_character, line)) from e 

427 # END control char exception handling 

428 

429 # Parse operation string for more info. 

430 # This makes no sense for symbolic refs, but we parse it anyway. 

431 old_commit: Union[AnyGitObject, None] = None 

432 is_tag_operation = False 

433 if "rejected" in operation: 

434 flags |= cls.REJECTED 

435 if "new tag" in operation: 

436 flags |= cls.NEW_TAG 

437 is_tag_operation = True 

438 if "tag update" in operation: 

439 flags |= cls.TAG_UPDATE 

440 is_tag_operation = True 

441 if "new branch" in operation: 

442 flags |= cls.NEW_HEAD 

443 if "..." in operation or ".." in operation: 

444 split_token = "..." 

445 if control_character == " ": 

446 split_token = split_token[:-1] 

447 old_commit = repo.rev_parse(operation.split(split_token)[0]) 

448 # END handle refspec 

449 

450 # Handle FETCH_HEAD and figure out ref type. 

451 # If we do not specify a target branch like master:refs/remotes/origin/master, 

452 # the fetch result is stored in FETCH_HEAD which destroys the rule we usually 

453 # have. In that case we use a symbolic reference which is detached. 

454 ref_type: Optional[Type[SymbolicReference]] = None 

455 if remote_local_ref_str == "FETCH_HEAD": 

456 ref_type = SymbolicReference 

457 elif ref_type_name == "tag" or is_tag_operation: 

458 # The ref_type_name can be branch, whereas we are still seeing a tag 

459 # operation. It happens during testing, which is based on actual git 

460 # operations. 

461 ref_type = TagReference 

462 elif ref_type_name in ("remote-tracking", "branch"): 

463 # Note: remote-tracking is just the first part of the 

464 # 'remote-tracking branch' token. We don't parse it correctly, but it's 

465 # enough to know what to do, and it's new in git 1.7something. 

466 ref_type = RemoteReference 

467 elif "/" in ref_type_name: 

468 # If the fetch spec look something like '+refs/pull/*:refs/heads/pull/*', 

469 # and is thus pretty much anything the user wants, we will have trouble 

470 # determining what's going on. For now, we assume the local ref is a Head. 

471 ref_type = Head 

472 else: 

473 raise TypeError("Cannot handle reference type: %r" % ref_type_name) 

474 # END handle ref type 

475 

476 # Create ref instance. 

477 if ref_type is SymbolicReference: 

478 remote_local_ref = ref_type(repo, "FETCH_HEAD") 

479 else: 

480 # Determine prefix. Tags are usually pulled into refs/tags; they may have 

481 # subdirectories. It is not clear sometimes where exactly the item is, 

482 # unless we have an absolute path as indicated by the 'ref/' prefix. 

483 # Otherwise even a tag could be in refs/remotes, which is when it will have 

484 # the 'tags/' subdirectory in its path. We don't want to test for actual 

485 # existence, but try to figure everything out analytically. 

486 ref_path: Optional[PathLike] = None 

487 remote_local_ref_str = remote_local_ref_str.strip() 

488 

489 if remote_local_ref_str.startswith(Reference._common_path_default + "/"): 

490 # Always use actual type if we get absolute paths. This will always be 

491 # the case if something is fetched outside of refs/remotes (if its not a 

492 # tag). 

493 ref_path = remote_local_ref_str 

494 if ref_type is not TagReference and not remote_local_ref_str.startswith( 

495 RemoteReference._common_path_default + "/" 

496 ): 

497 ref_type = Reference 

498 # END downgrade remote reference 

499 elif ref_type is TagReference and "tags/" in remote_local_ref_str: 

500 # Even though it's a tag, it is located in refs/remotes. 

501 ref_path = join_path(RemoteReference._common_path_default, remote_local_ref_str) 

502 else: 

503 ref_path = join_path(ref_type._common_path_default, remote_local_ref_str) 

504 # END obtain refpath 

505 

506 # Even though the path could be within the git conventions, we make sure we 

507 # respect whatever the user wanted, and disabled path checking. 

508 remote_local_ref = ref_type(repo, ref_path, check_path=False) 

509 # END create ref instance 

510 

511 note = (note and note.strip()) or "" 

512 

513 return cls(remote_local_ref, flags, note, old_commit, local_remote_ref) 

514 

515 @classmethod 

516 def iter_items(cls, repo: "Repo", *args: Any, **kwargs: Any) -> NoReturn: # -> Iterator['FetchInfo']: 

517 raise NotImplementedError 

518 

519 

520class Remote(LazyMixin, IterableObj): 

521 """Provides easy read and write access to a git remote. 

522 

523 Everything not part of this interface is considered an option for the current 

524 remote, allowing constructs like ``remote.pushurl`` to query the pushurl. 

525 

526 :note: 

527 When querying configuration, the configuration accessor will be cached to speed 

528 up subsequent accesses. 

529 """ 

530 

531 __slots__ = ("repo", "name", "_config_reader") 

532 

533 _id_attribute_ = "name" 

534 

535 unsafe_git_fetch_options = [ 

536 # This option allows users to execute arbitrary commands. 

537 # https://git-scm.com/docs/git-fetch#Documentation/git-fetch.txt---upload-packltupload-packgt 

538 "--upload-pack", 

539 ] 

540 unsafe_git_pull_options = [ 

541 # This option allows users to execute arbitrary commands. 

542 # https://git-scm.com/docs/git-pull#Documentation/git-pull.txt---upload-packltupload-packgt 

543 "--upload-pack" 

544 ] 

545 unsafe_git_push_options = [ 

546 # This option allows users to execute arbitrary commands. 

547 # https://git-scm.com/docs/git-push#Documentation/git-push.txt---execltgit-receive-packgt 

548 "--receive-pack", 

549 "--exec", 

550 ] 

551 

552 url: str # Obtained dynamically from _config_reader. See __getattr__ below. 

553 """The URL configured for the remote.""" 

554 

555 def __init__(self, repo: "Repo", name: str) -> None: 

556 """Initialize a remote instance. 

557 

558 :param repo: 

559 The repository we are a remote of. 

560 

561 :param name: 

562 The name of the remote, e.g. ``origin``. 

563 """ 

564 self.repo = repo 

565 self.name = name 

566 

567 def __getattr__(self, attr: str) -> Any: 

568 """Allows to call this instance like ``remote.special(*args, **kwargs)`` to 

569 call ``git remote special self.name``.""" 

570 if attr == "_config_reader": 

571 return super().__getattr__(attr) 

572 

573 # Sometimes, probably due to a bug in Python itself, we are being called even 

574 # though a slot of the same name exists. 

575 try: 

576 return self._config_reader.get(attr) 

577 except cp.NoOptionError: 

578 return super().__getattr__(attr) 

579 # END handle exception 

580 

581 def _config_section_name(self) -> str: 

582 return 'remote "%s"' % self.name 

583 

584 def _set_cache_(self, attr: str) -> None: 

585 if attr == "_config_reader": 

586 # NOTE: This is cached as __getattr__ is overridden to return remote config 

587 # values implicitly, such as in print(r.pushurl). 

588 self._config_reader = SectionConstraint( 

589 self.repo.config_reader("repository"), 

590 self._config_section_name(), 

591 ) 

592 else: 

593 super()._set_cache_(attr) 

594 

595 def __str__(self) -> str: 

596 return self.name 

597 

598 def __repr__(self) -> str: 

599 return '<git.%s "%s">' % (self.__class__.__name__, self.name) 

600 

601 def __eq__(self, other: object) -> bool: 

602 return isinstance(other, type(self)) and self.name == other.name 

603 

604 def __ne__(self, other: object) -> bool: 

605 return not (self == other) 

606 

607 def __hash__(self) -> int: 

608 return hash(self.name) 

609 

610 def exists(self) -> bool: 

611 """ 

612 :return: 

613 ``True`` if this is a valid, existing remote. 

614 Valid remotes have an entry in the repository's configuration. 

615 """ 

616 try: 

617 self.config_reader.get("url") 

618 return True 

619 except cp.NoOptionError: 

620 # We have the section at least... 

621 return True 

622 except cp.NoSectionError: 

623 return False 

624 

625 @classmethod 

626 def iter_items(cls, repo: "Repo", *args: Any, **kwargs: Any) -> Iterator["Remote"]: 

627 """:return: Iterator yielding :class:`Remote` objects of the given repository""" 

628 for section in repo.config_reader("repository").sections(): 

629 if not section.startswith("remote "): 

630 continue 

631 lbound = section.find('"') 

632 rbound = section.rfind('"') 

633 if lbound == -1 or rbound == -1: 

634 raise ValueError("Remote-Section has invalid format: %r" % section) 

635 yield Remote(repo, section[lbound + 1 : rbound]) 

636 # END for each configuration section 

637 

638 def set_url( 

639 self, new_url: str, old_url: Optional[str] = None, allow_unsafe_protocols: bool = False, **kwargs: Any 

640 ) -> "Remote": 

641 """Configure URLs on current remote (cf. command ``git remote set-url``). 

642 

643 This command manages URLs on the remote. 

644 

645 :param new_url: 

646 String being the URL to add as an extra remote URL. 

647 

648 :param old_url: 

649 When set, replaces this URL with `new_url` for the remote. 

650 

651 :param allow_unsafe_protocols: 

652 Allow unsafe protocols to be used, like ``ext``. 

653 

654 :return: 

655 self 

656 """ 

657 if not allow_unsafe_protocols: 

658 Git.check_unsafe_protocols(new_url) 

659 scmd = "set-url" 

660 kwargs["insert_kwargs_after"] = scmd 

661 if old_url: 

662 self.repo.git.remote(scmd, "--", self.name, new_url, old_url, **kwargs) 

663 else: 

664 self.repo.git.remote(scmd, "--", self.name, new_url, **kwargs) 

665 return self 

666 

667 def add_url(self, url: str, allow_unsafe_protocols: bool = False, **kwargs: Any) -> "Remote": 

668 """Adds a new url on current remote (special case of ``git remote set-url``). 

669 

670 This command adds new URLs to a given remote, making it possible to have 

671 multiple URLs for a single remote. 

672 

673 :param url: 

674 String being the URL to add as an extra remote URL. 

675 

676 :param allow_unsafe_protocols: 

677 Allow unsafe protocols to be used, like ``ext``. 

678 

679 :return: 

680 self 

681 """ 

682 return self.set_url(url, add=True, allow_unsafe_protocols=allow_unsafe_protocols) 

683 

684 def delete_url(self, url: str, **kwargs: Any) -> "Remote": 

685 """Deletes a new url on current remote (special case of ``git remote set-url``). 

686 

687 This command deletes new URLs to a given remote, making it possible to have 

688 multiple URLs for a single remote. 

689 

690 :param url: 

691 String being the URL to delete from the remote. 

692 

693 :return: 

694 self 

695 """ 

696 return self.set_url(url, delete=True) 

697 

698 @property 

699 def urls(self) -> Iterator[str]: 

700 """:return: Iterator yielding all configured URL targets on a remote as strings""" 

701 try: 

702 remote_details = self.repo.git.remote("get-url", "--all", self.name) 

703 assert isinstance(remote_details, str) 

704 for line in remote_details.split("\n"): 

705 yield line 

706 except GitCommandError as ex: 

707 ## We are on git < 2.7 (i.e TravisCI as of Oct-2016), 

708 # so `get-utl` command does not exist yet! 

709 # see: https://github.com/gitpython-developers/GitPython/pull/528#issuecomment-252976319 

710 # and: http://stackoverflow.com/a/32991784/548792 

711 # 

712 if "Unknown subcommand: get-url" in str(ex): 

713 try: 

714 remote_details = self.repo.git.remote("show", self.name) 

715 assert isinstance(remote_details, str) 

716 for line in remote_details.split("\n"): 

717 if " Push URL:" in line: 

718 yield line.split(": ")[-1] 

719 except GitCommandError as _ex: 

720 if any(msg in str(_ex) for msg in ["correct access rights", "cannot run ssh"]): 

721 # If ssh is not setup to access this repository, see issue 694. 

722 remote_details = self.repo.git.config("--get-all", "remote.%s.url" % self.name) 

723 assert isinstance(remote_details, str) 

724 for line in remote_details.split("\n"): 

725 yield line 

726 else: 

727 raise _ex 

728 else: 

729 raise ex 

730 

731 @property 

732 def refs(self) -> IterableList[RemoteReference]: 

733 """ 

734 :return: 

735 :class:`~git.util.IterableList` of :class:`~git.refs.remote.RemoteReference` 

736 objects. 

737 

738 It is prefixed, allowing you to omit the remote path portion, e.g.:: 

739 

740 remote.refs.master # yields RemoteReference('/refs/remotes/origin/master') 

741 """ 

742 out_refs: IterableList[RemoteReference] = IterableList(RemoteReference._id_attribute_, "%s/" % self.name) 

743 out_refs.extend(RemoteReference.list_items(self.repo, remote=self.name)) 

744 return out_refs 

745 

746 @property 

747 def stale_refs(self) -> IterableList[Reference]: 

748 """ 

749 :return: 

750 :class:`~git.util.IterableList` of :class:`~git.refs.remote.RemoteReference` 

751 objects that do not have a corresponding head in the remote reference 

752 anymore as they have been deleted on the remote side, but are still 

753 available locally. 

754 

755 The :class:`~git.util.IterableList` is prefixed, hence the 'origin' must be 

756 omitted. See :attr:`refs` property for an example. 

757 

758 To make things more complicated, it can be possible for the list to include 

759 other kinds of references, for example, tag references, if these are stale 

760 as well. This is a fix for the issue described here: 

761 https://github.com/gitpython-developers/GitPython/issues/260 

762 """ 

763 out_refs: IterableList[Reference] = IterableList(RemoteReference._id_attribute_, "%s/" % self.name) 

764 for line in self.repo.git.remote("prune", "--dry-run", self).splitlines()[2:]: 

765 # expecting 

766 # * [would prune] origin/new_branch 

767 token = " * [would prune] " 

768 if not line.startswith(token): 

769 continue 

770 ref_name = line.replace(token, "") 

771 # Sometimes, paths start with a full ref name, like refs/tags/foo. See #260. 

772 if ref_name.startswith(Reference._common_path_default + "/"): 

773 out_refs.append(Reference.from_path(self.repo, ref_name)) 

774 else: 

775 fqhn = "%s/%s" % (RemoteReference._common_path_default, ref_name) 

776 out_refs.append(RemoteReference(self.repo, fqhn)) 

777 # END special case handling 

778 # END for each line 

779 return out_refs 

780 

781 @classmethod 

782 def create(cls, repo: "Repo", name: str, url: str, allow_unsafe_protocols: bool = False, **kwargs: Any) -> "Remote": 

783 """Create a new remote to the given repository. 

784 

785 :param repo: 

786 Repository instance that is to receive the new remote. 

787 

788 :param name: 

789 Desired name of the remote. 

790 

791 :param url: 

792 URL which corresponds to the remote's name. 

793 

794 :param allow_unsafe_protocols: 

795 Allow unsafe protocols to be used, like ``ext``. 

796 

797 :param kwargs: 

798 Additional arguments to be passed to the ``git remote add`` command. 

799 

800 :return: 

801 New :class:`Remote` instance 

802 

803 :raise git.exc.GitCommandError: 

804 In case an origin with that name already exists. 

805 """ 

806 scmd = "add" 

807 kwargs["insert_kwargs_after"] = scmd 

808 url = Git.polish_url(url) 

809 if not allow_unsafe_protocols: 

810 Git.check_unsafe_protocols(url) 

811 repo.git.remote(scmd, "--", name, url, **kwargs) 

812 return cls(repo, name) 

813 

814 # `add` is an alias. 

815 @classmethod 

816 def add(cls, repo: "Repo", name: str, url: str, **kwargs: Any) -> "Remote": 

817 return cls.create(repo, name, url, **kwargs) 

818 

819 @classmethod 

820 def remove(cls, repo: "Repo", name: str) -> str: 

821 """Remove the remote with the given name. 

822 

823 :return: 

824 The passed remote name to remove 

825 """ 

826 repo.git.remote("rm", name) 

827 if isinstance(name, cls): 

828 name._clear_cache() 

829 return name 

830 

831 @classmethod 

832 def rm(cls, repo: "Repo", name: str) -> str: 

833 """Alias of remove. 

834 Remove the remote with the given name. 

835 

836 :return: 

837 The passed remote name to remove 

838 """ 

839 return cls.remove(repo, name) 

840 

841 def rename(self, new_name: str) -> "Remote": 

842 """Rename self to the given `new_name`. 

843 

844 :return: 

845 self 

846 """ 

847 if self.name == new_name: 

848 return self 

849 

850 self.repo.git.remote("rename", self.name, new_name) 

851 self.name = new_name 

852 self._clear_cache() 

853 

854 return self 

855 

856 def update(self, **kwargs: Any) -> "Remote": 

857 """Fetch all changes for this remote, including new branches which will be 

858 forced in (in case your local remote branch is not part the new remote branch's 

859 ancestry anymore). 

860 

861 :param kwargs: 

862 Additional arguments passed to ``git remote update``. 

863 

864 :return: 

865 self 

866 """ 

867 scmd = "update" 

868 kwargs["insert_kwargs_after"] = scmd 

869 self.repo.git.remote(scmd, self.name, **kwargs) 

870 return self 

871 

872 def _get_fetch_info_from_stderr( 

873 self, 

874 proc: "Git.AutoInterrupt", 

875 progress: Union[Callable[..., Any], RemoteProgress, None], 

876 kill_after_timeout: Union[None, float] = None, 

877 ) -> IterableList["FetchInfo"]: 

878 progress = to_progress_instance(progress) 

879 

880 # Skip first line as it is some remote info we are not interested in. 

881 output: IterableList["FetchInfo"] = IterableList("name") 

882 

883 # Lines which are no progress are fetch info lines. 

884 # This also waits for the command to finish. 

885 # Skip some progress lines that don't provide relevant information. 

886 fetch_info_lines = [] 

887 # Basically we want all fetch info lines which appear to be in regular form, and 

888 # thus have a command character. Everything else we ignore. 

889 cmds = set(FetchInfo._flag_map.keys()) 

890 

891 progress_handler = progress.new_message_handler() 

892 handle_process_output( 

893 proc, 

894 None, 

895 progress_handler, 

896 finalizer=None, 

897 decode_streams=True, 

898 kill_after_timeout=kill_after_timeout, 

899 ) 

900 

901 stderr_text = progress.error_lines and "\n".join(progress.error_lines) or "" 

902 proc.wait(stderr=stderr_text) 

903 if stderr_text: 

904 _logger.warning("Error lines received while fetching: %s", stderr_text) 

905 

906 for line in progress.other_lines: 

907 line = force_text(line) 

908 for cmd in cmds: 

909 if len(line) > 1 and line[0] == " " and line[1] == cmd: 

910 fetch_info_lines.append(line) 

911 continue 

912 

913 # Read head information. 

914 fetch_head = SymbolicReference(self.repo, "FETCH_HEAD") 

915 with open(fetch_head.abspath, "rb") as fp: 

916 fetch_head_info = [line.decode(defenc) for line in fp.readlines()] 

917 

918 l_fil = len(fetch_info_lines) 

919 l_fhi = len(fetch_head_info) 

920 if l_fil != l_fhi: 

921 msg = "Fetch head lines do not match lines provided via progress information\n" 

922 msg += "length of progress lines %i should be equal to lines in FETCH_HEAD file %i\n" 

923 msg += "Will ignore extra progress lines or fetch head lines." 

924 msg %= (l_fil, l_fhi) 

925 _logger.debug(msg) 

926 _logger.debug(b"info lines: " + str(fetch_info_lines).encode("UTF-8")) 

927 _logger.debug(b"head info: " + str(fetch_head_info).encode("UTF-8")) 

928 if l_fil < l_fhi: 

929 fetch_head_info = fetch_head_info[:l_fil] 

930 else: 

931 fetch_info_lines = fetch_info_lines[:l_fhi] 

932 # END truncate correct list 

933 # END sanity check + sanitization 

934 

935 for err_line, fetch_line in zip(fetch_info_lines, fetch_head_info): 

936 try: 

937 output.append(FetchInfo._from_line(self.repo, err_line, fetch_line)) 

938 except ValueError as exc: 

939 _logger.debug("Caught error while parsing line: %s", exc) 

940 _logger.warning("Git informed while fetching: %s", err_line.strip()) 

941 return output 

942 

943 def _get_push_info( 

944 self, 

945 proc: "Git.AutoInterrupt", 

946 progress: Union[Callable[..., Any], RemoteProgress, None], 

947 kill_after_timeout: Union[None, float] = None, 

948 ) -> PushInfoList: 

949 progress = to_progress_instance(progress) 

950 

951 # Read progress information from stderr. 

952 # We hope stdout can hold all the data, it should... 

953 # Read the lines manually as it will use carriage returns between the messages 

954 # to override the previous one. This is why we read the bytes manually. 

955 progress_handler = progress.new_message_handler() 

956 output: PushInfoList = PushInfoList() 

957 

958 def stdout_handler(line: str) -> None: 

959 try: 

960 output.append(PushInfo._from_line(self, line)) 

961 except ValueError: 

962 # If an error happens, additional info is given which we parse below. 

963 pass 

964 

965 handle_process_output( 

966 proc, 

967 stdout_handler, 

968 progress_handler, 

969 finalizer=None, 

970 decode_streams=False, 

971 kill_after_timeout=kill_after_timeout, 

972 ) 

973 stderr_text = progress.error_lines and "\n".join(progress.error_lines) or "" 

974 try: 

975 proc.wait(stderr=stderr_text) 

976 except Exception as e: 

977 # This is different than fetch (which fails if there is any stderr 

978 # even if there is an output). 

979 if not output: 

980 raise 

981 elif stderr_text: 

982 _logger.warning("Error lines received while fetching: %s", stderr_text) 

983 output.error = e 

984 

985 return output 

986 

987 def _assert_refspec(self) -> None: 

988 """Turns out we can't deal with remotes if the refspec is missing.""" 

989 config = self.config_reader 

990 unset = "placeholder" 

991 try: 

992 if config.get_value("fetch", default=unset) is unset: 

993 msg = "Remote '%s' has no refspec set.\n" 

994 msg += "You can set it as follows:" 

995 msg += " 'git config --add \"remote.%s.fetch +refs/heads/*:refs/heads/*\"'." 

996 raise AssertionError(msg % (self.name, self.name)) 

997 finally: 

998 config.release() 

999 

1000 def fetch( 

1001 self, 

1002 refspec: Union[str, List[str], None] = None, 

1003 progress: Union[RemoteProgress, None, "UpdateProgress"] = None, 

1004 verbose: bool = True, 

1005 kill_after_timeout: Union[None, float] = None, 

1006 allow_unsafe_protocols: bool = False, 

1007 allow_unsafe_options: bool = False, 

1008 **kwargs: Any, 

1009 ) -> IterableList[FetchInfo]: 

1010 """Fetch the latest changes for this remote. 

1011 

1012 :param refspec: 

1013 A "refspec" is used by fetch and push to describe the mapping 

1014 between remote ref and local ref. They are combined with a colon in 

1015 the format ``<src>:<dst>``, preceded by an optional plus sign, ``+``. 

1016 For example: ``git fetch $URL refs/heads/master:refs/heads/origin`` means 

1017 "grab the master branch head from the $URL and store it as my origin 

1018 branch head". And ``git push $URL refs/heads/master:refs/heads/to-upstream`` 

1019 means "publish my master branch head as to-upstream branch at $URL". 

1020 See also :manpage:`git-push(1)`. 

1021 

1022 Taken from the git manual, :manpage:`gitglossary(7)`. 

1023 

1024 Fetch supports multiple refspecs (as the underlying :manpage:`git-fetch(1)` 

1025 does) - supplying a list rather than a string for 'refspec' will make use of 

1026 this facility. 

1027 

1028 :param progress: 

1029 See the :meth:`push` method. 

1030 

1031 :param verbose: 

1032 Boolean for verbose output. 

1033 

1034 :param kill_after_timeout: 

1035 To specify a timeout in seconds for the git command, after which the process 

1036 should be killed. It is set to ``None`` by default. 

1037 

1038 :param allow_unsafe_protocols: 

1039 Allow unsafe protocols to be used, like ``ext``. 

1040 

1041 :param allow_unsafe_options: 

1042 Allow unsafe options to be used, like ``--upload-pack``. 

1043 

1044 :param kwargs: 

1045 Additional arguments to be passed to :manpage:`git-fetch(1)`. 

1046 

1047 :return: 

1048 IterableList(FetchInfo, ...) list of :class:`FetchInfo` instances providing 

1049 detailed information about the fetch results 

1050 

1051 :note: 

1052 As fetch does not provide progress information to non-ttys, we cannot make 

1053 it available here unfortunately as in the :meth:`push` method. 

1054 """ 

1055 if refspec is None: 

1056 # No argument refspec, then ensure the repo's config has a fetch refspec. 

1057 self._assert_refspec() 

1058 

1059 kwargs = add_progress(kwargs, self.repo.git, progress) 

1060 if isinstance(refspec, list): 

1061 args: Sequence[Optional[str]] = refspec 

1062 else: 

1063 args = [refspec] 

1064 

1065 if not allow_unsafe_protocols: 

1066 for ref in args: 

1067 if ref: 

1068 Git.check_unsafe_protocols(ref) 

1069 

1070 if not allow_unsafe_options: 

1071 Git.check_unsafe_options(options=list(kwargs.keys()), unsafe_options=self.unsafe_git_fetch_options) 

1072 

1073 proc = self.repo.git.fetch( 

1074 "--", self, *args, as_process=True, with_stdout=False, universal_newlines=False, v=verbose, **kwargs 

1075 ) 

1076 res = self._get_fetch_info_from_stderr(proc, progress, kill_after_timeout=kill_after_timeout) 

1077 if hasattr(self.repo.odb, "update_cache"): 

1078 self.repo.odb.update_cache() 

1079 return res 

1080 

1081 def pull( 

1082 self, 

1083 refspec: Union[str, List[str], None] = None, 

1084 progress: Union[RemoteProgress, "UpdateProgress", None] = None, 

1085 kill_after_timeout: Union[None, float] = None, 

1086 allow_unsafe_protocols: bool = False, 

1087 allow_unsafe_options: bool = False, 

1088 **kwargs: Any, 

1089 ) -> IterableList[FetchInfo]: 

1090 """Pull changes from the given branch, being the same as a fetch followed by a 

1091 merge of branch with your local branch. 

1092 

1093 :param refspec: 

1094 See :meth:`fetch` method. 

1095 

1096 :param progress: 

1097 See :meth:`push` method. 

1098 

1099 :param kill_after_timeout: 

1100 See :meth:`fetch` method. 

1101 

1102 :param allow_unsafe_protocols: 

1103 Allow unsafe protocols to be used, like ``ext``. 

1104 

1105 :param allow_unsafe_options: 

1106 Allow unsafe options to be used, like ``--upload-pack``. 

1107 

1108 :param kwargs: 

1109 Additional arguments to be passed to :manpage:`git-pull(1)`. 

1110 

1111 :return: 

1112 Please see :meth:`fetch` method. 

1113 """ 

1114 if refspec is None: 

1115 # No argument refspec, then ensure the repo's config has a fetch refspec. 

1116 self._assert_refspec() 

1117 kwargs = add_progress(kwargs, self.repo.git, progress) 

1118 

1119 refspec = Git._unpack_args(refspec or []) 

1120 if not allow_unsafe_protocols: 

1121 for ref in refspec: 

1122 Git.check_unsafe_protocols(ref) 

1123 

1124 if not allow_unsafe_options: 

1125 Git.check_unsafe_options(options=list(kwargs.keys()), unsafe_options=self.unsafe_git_pull_options) 

1126 

1127 proc = self.repo.git.pull( 

1128 "--", self, refspec, with_stdout=False, as_process=True, universal_newlines=False, v=True, **kwargs 

1129 ) 

1130 res = self._get_fetch_info_from_stderr(proc, progress, kill_after_timeout=kill_after_timeout) 

1131 if hasattr(self.repo.odb, "update_cache"): 

1132 self.repo.odb.update_cache() 

1133 return res 

1134 

1135 def push( 

1136 self, 

1137 refspec: Union[str, List[str], None] = None, 

1138 progress: Union[RemoteProgress, "UpdateProgress", Callable[..., RemoteProgress], None] = None, 

1139 kill_after_timeout: Union[None, float] = None, 

1140 allow_unsafe_protocols: bool = False, 

1141 allow_unsafe_options: bool = False, 

1142 **kwargs: Any, 

1143 ) -> PushInfoList: 

1144 """Push changes from source branch in refspec to target branch in refspec. 

1145 

1146 :param refspec: 

1147 See :meth:`fetch` method. 

1148 

1149 :param progress: 

1150 Can take one of many value types: 

1151 

1152 * ``None``, to discard progress information. 

1153 * A function (callable) that is called with the progress information. 

1154 Signature: ``progress(op_code, cur_count, max_count=None, message='')``. 

1155 See :meth:`RemoteProgress.update <git.util.RemoteProgress.update>` for a 

1156 description of all arguments given to the function. 

1157 * An instance of a class derived from :class:`~git.util.RemoteProgress` that 

1158 overrides the 

1159 :meth:`RemoteProgress.update <git.util.RemoteProgress.update>` method. 

1160 

1161 :note: 

1162 No further progress information is returned after push returns. 

1163 

1164 :param kill_after_timeout: 

1165 To specify a timeout in seconds for the git command, after which the process 

1166 should be killed. It is set to ``None`` by default. 

1167 

1168 :param allow_unsafe_protocols: 

1169 Allow unsafe protocols to be used, like ``ext``. 

1170 

1171 :param allow_unsafe_options: 

1172 Allow unsafe options to be used, like ``--receive-pack``. 

1173 

1174 :param kwargs: 

1175 Additional arguments to be passed to :manpage:`git-push(1)`. 

1176 

1177 :return: 

1178 A :class:`PushInfoList` object, where each list member represents an 

1179 individual head which had been updated on the remote side. 

1180 

1181 If the push contains rejected heads, these will have the 

1182 :const:`PushInfo.ERROR` bit set in their flags. 

1183 

1184 If the operation fails completely, the length of the returned 

1185 :class:`PushInfoList` will be 0. 

1186 

1187 Call :meth:`~PushInfoList.raise_if_error` on the returned object to raise on 

1188 any failure. 

1189 """ 

1190 kwargs = add_progress(kwargs, self.repo.git, progress) 

1191 

1192 refspec = Git._unpack_args(refspec or []) 

1193 if not allow_unsafe_protocols: 

1194 for ref in refspec: 

1195 Git.check_unsafe_protocols(ref) 

1196 

1197 if not allow_unsafe_options: 

1198 Git.check_unsafe_options(options=list(kwargs.keys()), unsafe_options=self.unsafe_git_push_options) 

1199 

1200 proc = self.repo.git.push( 

1201 "--", 

1202 self, 

1203 refspec, 

1204 porcelain=True, 

1205 as_process=True, 

1206 universal_newlines=True, 

1207 kill_after_timeout=kill_after_timeout, 

1208 **kwargs, 

1209 ) 

1210 return self._get_push_info(proc, progress, kill_after_timeout=kill_after_timeout) 

1211 

1212 @property 

1213 def config_reader(self) -> SectionConstraint[GitConfigParser]: 

1214 """ 

1215 :return: 

1216 :class:`~git.config.GitConfigParser` compatible object able to read options 

1217 for only our remote. Hence you may simply type ``config.get("pushurl")`` to 

1218 obtain the information. 

1219 """ 

1220 return self._config_reader 

1221 

1222 def _clear_cache(self) -> None: 

1223 try: 

1224 del self._config_reader 

1225 except AttributeError: 

1226 pass 

1227 # END handle exception 

1228 

1229 @property 

1230 def config_writer(self) -> SectionConstraint: 

1231 """ 

1232 :return: 

1233 :class:`~git.config.GitConfigParser`-compatible object able to write options 

1234 for this remote. 

1235 

1236 :note: 

1237 You can only own one writer at a time - delete it to release the 

1238 configuration file and make it usable by others. 

1239 

1240 To assure consistent results, you should only query options through the 

1241 writer. Once you are done writing, you are free to use the config reader 

1242 once again. 

1243 """ 

1244 writer = self.repo.config_writer() 

1245 

1246 # Clear our cache to ensure we re-read the possibly changed configuration. 

1247 self._clear_cache() 

1248 return SectionConstraint(writer, self._config_section_name())