You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

649 lines
26KB

  1. # test_remote.py
  2. # Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors
  3. #
  4. # This module is part of GitPython and is released under
  5. # the BSD License: http://www.opensource.org/licenses/bsd-license.php
  6. import random
  7. import tempfile
  8. from unittest import skipIf
  9. from git import (
  10. RemoteProgress,
  11. FetchInfo,
  12. Reference,
  13. SymbolicReference,
  14. Head,
  15. Commit,
  16. PushInfo,
  17. RemoteReference,
  18. TagReference,
  19. Remote,
  20. GitCommandError
  21. )
  22. from git.cmd import Git
  23. from git.compat import string_types
  24. from git.test.lib import (
  25. TestBase,
  26. with_rw_repo,
  27. with_rw_and_rw_remote_repo,
  28. fixture,
  29. GIT_DAEMON_PORT,
  30. assert_raises
  31. )
  32. from git.util import IterableList, rmtree, HIDE_WINDOWS_FREEZE_ERRORS
  33. import os.path as osp
  34. # assure we have repeatable results
  35. random.seed(0)
  36. class TestRemoteProgress(RemoteProgress):
  37. __slots__ = ("_seen_lines", "_stages_per_op", '_num_progress_messages')
  38. def __init__(self):
  39. super(TestRemoteProgress, self).__init__()
  40. self._seen_lines = []
  41. self._stages_per_op = {}
  42. self._num_progress_messages = 0
  43. def _parse_progress_line(self, line):
  44. # we may remove the line later if it is dropped
  45. # Keep it for debugging
  46. self._seen_lines.append(line)
  47. rval = super(TestRemoteProgress, self)._parse_progress_line(line)
  48. assert len(line) > 1, "line %r too short" % line
  49. return rval
  50. def line_dropped(self, line):
  51. try:
  52. self._seen_lines.remove(line)
  53. except ValueError:
  54. pass
  55. def update(self, op_code, cur_count, max_count=None, message=''):
  56. # check each stage only comes once
  57. op_id = op_code & self.OP_MASK
  58. assert op_id in (self.COUNTING, self.COMPRESSING, self.WRITING)
  59. if op_code & self.WRITING > 0:
  60. if op_code & self.BEGIN > 0:
  61. assert not message, 'should not have message when remote begins writing'
  62. elif op_code & self.END > 0:
  63. assert message
  64. assert not message.startswith(', '), "Sanitize progress messages: '%s'" % message
  65. assert not message.endswith(', '), "Sanitize progress messages: '%s'" % message
  66. self._stages_per_op.setdefault(op_id, 0)
  67. self._stages_per_op[op_id] = self._stages_per_op[op_id] | (op_code & self.STAGE_MASK)
  68. if op_code & (self.WRITING | self.END) == (self.WRITING | self.END):
  69. assert message
  70. # END check we get message
  71. self._num_progress_messages += 1
  72. def make_assertion(self):
  73. # we don't always receive messages
  74. if not self._seen_lines:
  75. return
  76. # sometimes objects are not compressed which is okay
  77. assert len(self._seen_ops) in (2, 3), len(self._seen_ops)
  78. assert self._stages_per_op
  79. # must have seen all stages
  80. for op, stages in self._stages_per_op.items(): # @UnusedVariable
  81. assert stages & self.STAGE_MASK == self.STAGE_MASK
  82. # END for each op/stage
  83. def assert_received_message(self):
  84. assert self._num_progress_messages
  85. class TestRemote(TestBase):
  86. def tearDown(self):
  87. import gc
  88. gc.collect()
  89. def _print_fetchhead(self, repo):
  90. with open(osp.join(repo.git_dir, "FETCH_HEAD")):
  91. pass
  92. def _do_test_fetch_result(self, results, remote):
  93. # self._print_fetchhead(remote.repo)
  94. self.assertGreater(len(results), 0)
  95. self.assertIsInstance(results[0], FetchInfo)
  96. for info in results:
  97. self.assertIsInstance(info.note, string_types)
  98. if isinstance(info.ref, Reference):
  99. self.assertTrue(info.flags)
  100. # END reference type flags handling
  101. self.assertIsInstance(info.ref, (SymbolicReference, Reference))
  102. if info.flags & (info.FORCED_UPDATE | info.FAST_FORWARD):
  103. self.assertIsInstance(info.old_commit, Commit)
  104. else:
  105. self.assertIsNone(info.old_commit)
  106. # END forced update checking
  107. # END for each info
  108. def _do_test_push_result(self, results, remote):
  109. self.assertGreater(len(results), 0)
  110. self.assertIsInstance(results[0], PushInfo)
  111. for info in results:
  112. self.assertTrue(info.flags)
  113. self.assertIsInstance(info.summary, string_types)
  114. if info.old_commit is not None:
  115. self.assertIsInstance(info.old_commit, Commit)
  116. if info.flags & info.ERROR:
  117. has_one = False
  118. for bitflag in (info.REJECTED, info.REMOTE_REJECTED, info.REMOTE_FAILURE):
  119. has_one |= bool(info.flags & bitflag)
  120. # END for each bitflag
  121. self.assertTrue(has_one)
  122. else:
  123. # there must be a remote commit
  124. if info.flags & info.DELETED == 0:
  125. self.assertIsInstance(info.local_ref, Reference)
  126. else:
  127. self.assertIsNone(info.local_ref)
  128. self.assertIn(type(info.remote_ref), (TagReference, RemoteReference))
  129. # END error checking
  130. # END for each info
  131. def _do_test_fetch_info(self, repo):
  132. self.failUnlessRaises(ValueError, FetchInfo._from_line, repo, "nonsense", '')
  133. self.failUnlessRaises(
  134. ValueError, FetchInfo._from_line, repo, "? [up to date] 0.1.7RC -> origin/0.1.7RC", '')
  135. def _commit_random_file(self, repo):
  136. # Create a file with a random name and random data and commit it to repo.
  137. # Return the committed absolute file path
  138. index = repo.index
  139. new_file = self._make_file(osp.basename(tempfile.mktemp()), str(random.random()), repo)
  140. index.add([new_file])
  141. index.commit("Committing %s" % new_file)
  142. return new_file
  143. def _do_test_fetch(self, remote, rw_repo, remote_repo):
  144. # specialized fetch testing to de-clutter the main test
  145. self._do_test_fetch_info(rw_repo)
  146. def fetch_and_test(remote, **kwargs):
  147. progress = TestRemoteProgress()
  148. kwargs['progress'] = progress
  149. res = remote.fetch(**kwargs)
  150. progress.make_assertion()
  151. self._do_test_fetch_result(res, remote)
  152. return res
  153. # END fetch and check
  154. def get_info(res, remote, name):
  155. return res["%s/%s" % (remote, name)]
  156. # put remote head to master as it is guaranteed to exist
  157. remote_repo.head.reference = remote_repo.heads.master
  158. res = fetch_and_test(remote)
  159. # all up to date
  160. for info in res:
  161. self.assertTrue(info.flags & info.HEAD_UPTODATE)
  162. # rewind remote head to trigger rejection
  163. # index must be false as remote is a bare repo
  164. rhead = remote_repo.head
  165. remote_commit = rhead.commit
  166. rhead.reset("HEAD~2", index=False)
  167. res = fetch_and_test(remote)
  168. mkey = "%s/%s" % (remote, 'master')
  169. master_info = res[mkey]
  170. self.assertTrue(master_info.flags & FetchInfo.FORCED_UPDATE)
  171. self.assertIsNotNone(master_info.note)
  172. # normal fast forward - set head back to previous one
  173. rhead.commit = remote_commit
  174. res = fetch_and_test(remote)
  175. self.assertTrue(res[mkey].flags & FetchInfo.FAST_FORWARD)
  176. # new remote branch
  177. new_remote_branch = Head.create(remote_repo, "new_branch")
  178. res = fetch_and_test(remote)
  179. new_branch_info = get_info(res, remote, new_remote_branch)
  180. self.assertTrue(new_branch_info.flags & FetchInfo.NEW_HEAD)
  181. # remote branch rename ( causes creation of a new one locally )
  182. new_remote_branch.rename("other_branch_name")
  183. res = fetch_and_test(remote)
  184. other_branch_info = get_info(res, remote, new_remote_branch)
  185. self.assertEqual(other_branch_info.ref.commit, new_branch_info.ref.commit)
  186. # remove new branch
  187. Head.delete(new_remote_branch.repo, new_remote_branch)
  188. res = fetch_and_test(remote)
  189. # deleted remote will not be fetched
  190. self.failUnlessRaises(IndexError, get_info, res, remote, new_remote_branch)
  191. # prune stale tracking branches
  192. stale_refs = remote.stale_refs
  193. self.assertEqual(len(stale_refs), 2)
  194. self.assertIsInstance(stale_refs[0], RemoteReference)
  195. RemoteReference.delete(rw_repo, *stale_refs)
  196. # test single branch fetch with refspec including target remote
  197. res = fetch_and_test(remote, refspec="master:refs/remotes/%s/master" % remote)
  198. self.assertEqual(len(res), 1)
  199. self.assertTrue(get_info(res, remote, 'master'))
  200. # ... with respec and no target
  201. res = fetch_and_test(remote, refspec='master')
  202. self.assertEqual(len(res), 1)
  203. # ... multiple refspecs ... works, but git command returns with error if one ref is wrong without
  204. # doing anything. This is new in later binaries
  205. # res = fetch_and_test(remote, refspec=['master', 'fred'])
  206. # self.assertEqual(len(res), 1)
  207. # add new tag reference
  208. rtag = TagReference.create(remote_repo, "1.0-RV_hello.there")
  209. res = fetch_and_test(remote, tags=True)
  210. tinfo = res[str(rtag)]
  211. self.assertIsInstance(tinfo.ref, TagReference)
  212. self.assertEqual(tinfo.ref.commit, rtag.commit)
  213. self.assertTrue(tinfo.flags & tinfo.NEW_TAG)
  214. # adjust the local tag commit
  215. Reference.set_object(rtag, rhead.commit.parents[0].parents[0])
  216. # as of git 2.20 one cannot clobber local tags that have changed without
  217. # specifying --force, and the test assumes you can clobber, so...
  218. force = None
  219. if rw_repo.git.version_info[:2] >= (2, 20):
  220. force = True
  221. res = fetch_and_test(remote, tags=True, force=force)
  222. tinfo = res[str(rtag)]
  223. self.assertEqual(tinfo.commit, rtag.commit)
  224. self.assertTrue(tinfo.flags & tinfo.TAG_UPDATE)
  225. # delete remote tag - local one will stay
  226. TagReference.delete(remote_repo, rtag)
  227. res = fetch_and_test(remote, tags=True)
  228. self.failUnlessRaises(IndexError, get_info, res, remote, str(rtag))
  229. # provoke to receive actual objects to see what kind of output we have to
  230. # expect. For that we need a remote transport protocol
  231. # Create a new UN-shared repo and fetch into it after we pushed a change
  232. # to the shared repo
  233. other_repo_dir = tempfile.mktemp("other_repo")
  234. # must clone with a local path for the repo implementation not to freak out
  235. # as it wants local paths only ( which I can understand )
  236. other_repo = remote_repo.clone(other_repo_dir, shared=False)
  237. remote_repo_url = osp.basename(remote_repo.git_dir) # git-daemon runs with appropriate `--base-path`.
  238. remote_repo_url = Git.polish_url("git://localhost:%s/%s" % (GIT_DAEMON_PORT, remote_repo_url))
  239. # put origin to git-url
  240. other_origin = other_repo.remotes.origin
  241. with other_origin.config_writer as cw:
  242. cw.set("url", remote_repo_url)
  243. # it automatically creates alternates as remote_repo is shared as well.
  244. # It will use the transport though and ignore alternates when fetching
  245. # assert not other_repo.alternates # this would fail
  246. # assure we are in the right state
  247. rw_repo.head.reset(remote.refs.master, working_tree=True)
  248. try:
  249. self._commit_random_file(rw_repo)
  250. remote.push(rw_repo.head.reference)
  251. # here I would expect to see remote-information about packing
  252. # objects and so on. Unfortunately, this does not happen
  253. # if we are redirecting the output - git explicitly checks for this
  254. # and only provides progress information to ttys
  255. res = fetch_and_test(other_origin)
  256. finally:
  257. rmtree(other_repo_dir)
  258. # END test and cleanup
  259. def _assert_push_and_pull(self, remote, rw_repo, remote_repo):
  260. # push our changes
  261. lhead = rw_repo.head
  262. # assure we are on master and it is checked out where the remote is
  263. try:
  264. lhead.reference = rw_repo.heads.master
  265. except AttributeError:
  266. # if the author is on a non-master branch, the clones might not have
  267. # a local master yet. We simply create it
  268. lhead.reference = rw_repo.create_head('master')
  269. # END master handling
  270. lhead.reset(remote.refs.master, working_tree=True)
  271. # push without spec should fail ( without further configuration )
  272. # well, works nicely
  273. # self.failUnlessRaises(GitCommandError, remote.push)
  274. # simple file push
  275. self._commit_random_file(rw_repo)
  276. progress = TestRemoteProgress()
  277. res = remote.push(lhead.reference, progress)
  278. self.assertIsInstance(res, IterableList)
  279. self._do_test_push_result(res, remote)
  280. progress.make_assertion()
  281. # rejected - undo last commit
  282. lhead.reset("HEAD~1")
  283. res = remote.push(lhead.reference)
  284. self.assertTrue(res[0].flags & PushInfo.ERROR)
  285. self.assertTrue(res[0].flags & PushInfo.REJECTED)
  286. self._do_test_push_result(res, remote)
  287. # force rejected pull
  288. res = remote.push('+%s' % lhead.reference)
  289. self.assertEqual(res[0].flags & PushInfo.ERROR, 0)
  290. self.assertTrue(res[0].flags & PushInfo.FORCED_UPDATE)
  291. self._do_test_push_result(res, remote)
  292. # invalid refspec
  293. self.failUnlessRaises(GitCommandError, remote.push, "hellothere")
  294. # push new tags
  295. progress = TestRemoteProgress()
  296. to_be_updated = "my_tag.1.0RV"
  297. new_tag = TagReference.create(rw_repo, to_be_updated) # @UnusedVariable
  298. other_tag = TagReference.create(rw_repo, "my_obj_tag.2.1aRV", message="my message")
  299. res = remote.push(progress=progress, tags=True)
  300. self.assertTrue(res[-1].flags & PushInfo.NEW_TAG)
  301. progress.make_assertion()
  302. self._do_test_push_result(res, remote)
  303. # update push new tags
  304. # Rejection is default
  305. new_tag = TagReference.create(rw_repo, to_be_updated, ref='HEAD~1', force=True)
  306. res = remote.push(tags=True)
  307. self._do_test_push_result(res, remote)
  308. self.assertTrue(res[-1].flags & PushInfo.REJECTED)
  309. self.assertTrue(res[-1].flags & PushInfo.ERROR)
  310. # push force this tag
  311. res = remote.push("+%s" % new_tag.path)
  312. self.assertEqual(res[-1].flags & PushInfo.ERROR, 0)
  313. self.assertTrue(res[-1].flags & PushInfo.FORCED_UPDATE)
  314. # delete tag - have to do it using refspec
  315. res = remote.push(":%s" % new_tag.path)
  316. self._do_test_push_result(res, remote)
  317. self.assertTrue(res[0].flags & PushInfo.DELETED)
  318. # Currently progress is not properly transferred, especially not using
  319. # the git daemon
  320. # progress.assert_received_message()
  321. # push new branch
  322. new_head = Head.create(rw_repo, "my_new_branch")
  323. progress = TestRemoteProgress()
  324. res = remote.push(new_head, progress)
  325. self.assertGreater(len(res), 0)
  326. self.assertTrue(res[0].flags & PushInfo.NEW_HEAD)
  327. progress.make_assertion()
  328. self._do_test_push_result(res, remote)
  329. # delete new branch on the remote end and locally
  330. res = remote.push(":%s" % new_head.path)
  331. self._do_test_push_result(res, remote)
  332. Head.delete(rw_repo, new_head)
  333. self.assertTrue(res[-1].flags & PushInfo.DELETED)
  334. # --all
  335. res = remote.push(all=True)
  336. self._do_test_push_result(res, remote)
  337. remote.pull('master')
  338. # cleanup - delete created tags and branches as we are in an innerloop on
  339. # the same repository
  340. TagReference.delete(rw_repo, new_tag, other_tag)
  341. remote.push(":%s" % other_tag.path)
  342. @skipIf(HIDE_WINDOWS_FREEZE_ERRORS, "FIXME: Freezes!")
  343. @with_rw_and_rw_remote_repo('0.1.6')
  344. def test_base(self, rw_repo, remote_repo):
  345. num_remotes = 0
  346. remote_set = set()
  347. ran_fetch_test = False
  348. for remote in rw_repo.remotes:
  349. num_remotes += 1
  350. self.assertEqual(remote, remote)
  351. self.assertNotEqual(str(remote), repr(remote))
  352. remote_set.add(remote)
  353. remote_set.add(remote) # should already exist
  354. # REFS
  355. refs = remote.refs
  356. self.assertTrue(refs)
  357. for ref in refs:
  358. self.assertEqual(ref.remote_name, remote.name)
  359. self.assertTrue(ref.remote_head)
  360. # END for each ref
  361. # OPTIONS
  362. # cannot use 'fetch' key anymore as it is now a method
  363. for opt in ("url",):
  364. val = getattr(remote, opt)
  365. reader = remote.config_reader
  366. assert reader.get(opt) == val
  367. assert reader.get_value(opt, None) == val
  368. # unable to write with a reader
  369. self.failUnlessRaises(IOError, reader.set, opt, "test")
  370. # change value
  371. with remote.config_writer as writer:
  372. new_val = "myval"
  373. writer.set(opt, new_val)
  374. assert writer.get(opt) == new_val
  375. writer.set(opt, val)
  376. assert writer.get(opt) == val
  377. assert getattr(remote, opt) == val
  378. # END for each default option key
  379. # RENAME
  380. other_name = "totally_other_name"
  381. prev_name = remote.name
  382. self.assertEqual(remote.rename(other_name), remote)
  383. self.assertNotEqual(prev_name, remote.name)
  384. # multiple times
  385. for _ in range(2):
  386. self.assertEqual(remote.rename(prev_name).name, prev_name)
  387. # END for each rename ( back to prev_name )
  388. # PUSH/PULL TESTING
  389. self._assert_push_and_pull(remote, rw_repo, remote_repo)
  390. # FETCH TESTING
  391. # Only for remotes - local cases are the same or less complicated
  392. # as additional progress information will never be emitted
  393. if remote.name == "daemon_origin":
  394. self._do_test_fetch(remote, rw_repo, remote_repo)
  395. ran_fetch_test = True
  396. # END fetch test
  397. remote.update()
  398. # END for each remote
  399. self.assertTrue(ran_fetch_test)
  400. self.assertTrue(num_remotes)
  401. self.assertEqual(num_remotes, len(remote_set))
  402. origin = rw_repo.remote('origin')
  403. assert origin == rw_repo.remotes.origin
  404. # Verify we can handle prunes when fetching
  405. # stderr lines look like this: x [deleted] (none) -> origin/experiment-2012
  406. # These should just be skipped
  407. # If we don't have a manual checkout, we can't actually assume there are any non-master branches
  408. remote_repo.create_head("myone_for_deletion")
  409. # Get the branch - to be pruned later
  410. origin.fetch()
  411. num_deleted = False
  412. for branch in remote_repo.heads:
  413. if branch.name != 'master':
  414. branch.delete(remote_repo, branch, force=True)
  415. num_deleted += 1
  416. # end
  417. # end for each branch
  418. self.assertGreater(num_deleted, 0)
  419. self.assertEqual(len(rw_repo.remotes.origin.fetch(prune=True)), 1, "deleted everything but master")
  420. @with_rw_repo('HEAD', bare=True)
  421. def test_creation_and_removal(self, bare_rw_repo):
  422. new_name = "test_new_one"
  423. arg_list = (new_name, "git@server:hello.git")
  424. remote = Remote.create(bare_rw_repo, *arg_list)
  425. self.assertEqual(remote.name, "test_new_one")
  426. self.assertIn(remote, bare_rw_repo.remotes)
  427. self.assertTrue(remote.exists())
  428. # create same one again
  429. self.failUnlessRaises(GitCommandError, Remote.create, bare_rw_repo, *arg_list)
  430. Remote.remove(bare_rw_repo, new_name)
  431. self.assertTrue(remote.exists()) # We still have a cache that doesn't know we were deleted by name
  432. remote._clear_cache()
  433. assert not remote.exists() # Cache should be renewed now. This is an issue ...
  434. for remote in bare_rw_repo.remotes:
  435. if remote.name == new_name:
  436. raise AssertionError("Remote removal failed")
  437. # END if deleted remote matches existing remote's name
  438. # END for each remote
  439. # Issue #262 - the next call would fail if bug wasn't fixed
  440. bare_rw_repo.create_remote('bogus', '/bogus/path', mirror='push')
  441. def test_fetch_info(self):
  442. # assure we can handle remote-tracking branches
  443. fetch_info_line_fmt = "c437ee5deb8d00cf02f03720693e4c802e99f390 not-for-merge %s '0.3' of "
  444. fetch_info_line_fmt += "git://github.com/gitpython-developers/GitPython"
  445. remote_info_line_fmt = "* [new branch] nomatter -> %s"
  446. self.failUnlessRaises(ValueError, FetchInfo._from_line, self.rorepo,
  447. remote_info_line_fmt % "refs/something/branch",
  448. "269c498e56feb93e408ed4558c8138d750de8893\t\t/Users/ben/test/foo\n")
  449. fi = FetchInfo._from_line(self.rorepo,
  450. remote_info_line_fmt % "local/master",
  451. fetch_info_line_fmt % 'remote-tracking branch')
  452. assert not fi.ref.is_valid()
  453. self.assertEqual(fi.ref.name, "local/master")
  454. # handles non-default refspecs: One can specify a different path in refs/remotes
  455. # or a special path just in refs/something for instance
  456. fi = FetchInfo._from_line(self.rorepo,
  457. remote_info_line_fmt % "subdir/tagname",
  458. fetch_info_line_fmt % 'tag')
  459. self.assertIsInstance(fi.ref, TagReference)
  460. assert fi.ref.path.startswith('refs/tags'), fi.ref.path
  461. # it could be in a remote direcftory though
  462. fi = FetchInfo._from_line(self.rorepo,
  463. remote_info_line_fmt % "remotename/tags/tagname",
  464. fetch_info_line_fmt % 'tag')
  465. self.assertIsInstance(fi.ref, TagReference)
  466. assert fi.ref.path.startswith('refs/remotes/'), fi.ref.path
  467. # it can also be anywhere !
  468. tag_path = "refs/something/remotename/tags/tagname"
  469. fi = FetchInfo._from_line(self.rorepo,
  470. remote_info_line_fmt % tag_path,
  471. fetch_info_line_fmt % 'tag')
  472. self.assertIsInstance(fi.ref, TagReference)
  473. self.assertEqual(fi.ref.path, tag_path)
  474. # branches default to refs/remotes
  475. fi = FetchInfo._from_line(self.rorepo,
  476. remote_info_line_fmt % "remotename/branch",
  477. fetch_info_line_fmt % 'branch')
  478. self.assertIsInstance(fi.ref, RemoteReference)
  479. self.assertEqual(fi.ref.remote_name, 'remotename')
  480. # but you can force it anywhere, in which case we only have a references
  481. fi = FetchInfo._from_line(self.rorepo,
  482. remote_info_line_fmt % "refs/something/branch",
  483. fetch_info_line_fmt % 'branch')
  484. assert type(fi.ref) is Reference, type(fi.ref)
  485. self.assertEqual(fi.ref.path, "refs/something/branch")
  486. def test_uncommon_branch_names(self):
  487. stderr_lines = fixture('uncommon_branch_prefix_stderr').decode('ascii').splitlines()
  488. fetch_lines = fixture('uncommon_branch_prefix_FETCH_HEAD').decode('ascii').splitlines()
  489. # The contents of the files above must be fetched with a custom refspec:
  490. # +refs/pull/*:refs/heads/pull/*
  491. res = [FetchInfo._from_line('ShouldntMatterRepo', stderr, fetch_line)
  492. for stderr, fetch_line in zip(stderr_lines, fetch_lines)]
  493. self.assertGreater(len(res), 0)
  494. self.assertEqual(res[0].remote_ref_path, 'refs/pull/1/head')
  495. self.assertEqual(res[0].ref.path, 'refs/heads/pull/1/head')
  496. self.assertIsInstance(res[0].ref, Head)
  497. @with_rw_repo('HEAD', bare=False)
  498. def test_multiple_urls(self, rw_repo):
  499. # test addresses
  500. test1 = 'https://github.com/gitpython-developers/GitPython'
  501. test2 = 'https://github.com/gitpython-developers/gitdb'
  502. test3 = 'https://github.com/gitpython-developers/smmap'
  503. remote = rw_repo.remotes[0]
  504. # Testing setting a single URL
  505. remote.set_url(test1)
  506. self.assertEqual(list(remote.urls), [test1])
  507. # Testing replacing that single URL
  508. remote.set_url(test1)
  509. self.assertEqual(list(remote.urls), [test1])
  510. # Testing adding new URLs
  511. remote.set_url(test2, add=True)
  512. self.assertEqual(list(remote.urls), [test1, test2])
  513. remote.set_url(test3, add=True)
  514. self.assertEqual(list(remote.urls), [test1, test2, test3])
  515. # Testing removing an URL
  516. remote.set_url(test2, delete=True)
  517. self.assertEqual(list(remote.urls), [test1, test3])
  518. # Testing changing an URL
  519. remote.set_url(test2, test3)
  520. self.assertEqual(list(remote.urls), [test1, test2])
  521. # will raise: fatal: --add --delete doesn't make sense
  522. assert_raises(GitCommandError, remote.set_url, test2, add=True, delete=True)
  523. # Testing on another remote, with the add/delete URL
  524. remote = rw_repo.create_remote('another', url=test1)
  525. remote.add_url(test2)
  526. self.assertEqual(list(remote.urls), [test1, test2])
  527. remote.add_url(test3)
  528. self.assertEqual(list(remote.urls), [test1, test2, test3])
  529. # Testing removing all the URLs
  530. remote.delete_url(test2)
  531. self.assertEqual(list(remote.urls), [test1, test3])
  532. remote.delete_url(test1)
  533. self.assertEqual(list(remote.urls), [test3])
  534. # will raise fatal: Will not delete all non-push URLs
  535. assert_raises(GitCommandError, remote.delete_url, test3)
  536. def test_fetch_error(self):
  537. rem = self.rorepo.remote('origin')
  538. with self.assertRaisesRegex(GitCommandError, "[Cc]ouldn't find remote ref __BAD_REF__"):
  539. rem.fetch('__BAD_REF__')
  540. @with_rw_repo('0.1.6', bare=False)
  541. def test_push_error(self, repo):
  542. rem = repo.remote('origin')
  543. with self.assertRaisesRegex(GitCommandError, "src refspec __BAD_REF__ does not match any"):
  544. rem.push('__BAD_REF__')