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.

569 lines
23KB

  1. # test_refs.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. from itertools import chain
  7. from git import (
  8. Reference,
  9. Head,
  10. TagReference,
  11. RemoteReference,
  12. Commit,
  13. SymbolicReference,
  14. GitCommandError,
  15. RefLog
  16. )
  17. from git.objects.tag import TagObject
  18. from git.test.lib import (
  19. TestBase,
  20. with_rw_repo
  21. )
  22. from git.util import Actor
  23. import git.refs as refs
  24. import os.path as osp
  25. class TestRefs(TestBase):
  26. def test_from_path(self):
  27. # should be able to create any reference directly
  28. for ref_type in (Reference, Head, TagReference, RemoteReference):
  29. for name in ('rela_name', 'path/rela_name'):
  30. full_path = ref_type.to_full_path(name)
  31. instance = ref_type.from_path(self.rorepo, full_path)
  32. assert isinstance(instance, ref_type)
  33. # END for each name
  34. # END for each type
  35. # invalid path
  36. self.failUnlessRaises(ValueError, TagReference, self.rorepo, "refs/invalid/tag")
  37. # works without path check
  38. TagReference(self.rorepo, "refs/invalid/tag", check_path=False)
  39. def test_tag_base(self):
  40. tag_object_refs = []
  41. for tag in self.rorepo.tags:
  42. assert "refs/tags" in tag.path
  43. assert tag.name
  44. assert isinstance(tag.commit, Commit)
  45. if tag.tag is not None:
  46. tag_object_refs.append(tag)
  47. tagobj = tag.tag
  48. # have no dict
  49. self.failUnlessRaises(AttributeError, setattr, tagobj, 'someattr', 1)
  50. assert isinstance(tagobj, TagObject)
  51. assert tagobj.tag == tag.name
  52. assert isinstance(tagobj.tagger, Actor)
  53. assert isinstance(tagobj.tagged_date, int)
  54. assert isinstance(tagobj.tagger_tz_offset, int)
  55. assert tagobj.message
  56. assert tag.object == tagobj
  57. # can't assign the object
  58. self.failUnlessRaises(AttributeError, setattr, tag, 'object', tagobj)
  59. # END if we have a tag object
  60. # END for tag in repo-tags
  61. assert tag_object_refs
  62. assert isinstance(self.rorepo.tags['0.1.5'], TagReference)
  63. def test_tags_author(self):
  64. tag = self.rorepo.tags[0]
  65. tagobj = tag.tag
  66. assert isinstance(tagobj.tagger, Actor)
  67. tagger_name = tagobj.tagger.name
  68. assert tagger_name == 'Michael Trier'
  69. def test_tags(self):
  70. # tag refs can point to tag objects or to commits
  71. s = set()
  72. ref_count = 0
  73. for ref in chain(self.rorepo.tags, self.rorepo.heads):
  74. ref_count += 1
  75. assert isinstance(ref, refs.Reference)
  76. assert str(ref) == ref.name
  77. assert repr(ref)
  78. assert ref == ref
  79. assert not ref != ref
  80. s.add(ref)
  81. # END for each ref
  82. assert len(s) == ref_count
  83. assert len(s | s) == ref_count
  84. @with_rw_repo('HEAD', bare=False)
  85. def test_heads(self, rwrepo):
  86. for head in rwrepo.heads:
  87. assert head.name
  88. assert head.path
  89. assert "refs/heads" in head.path
  90. prev_object = head.object
  91. cur_object = head.object
  92. assert prev_object == cur_object # represent the same git object
  93. assert prev_object is not cur_object # but are different instances
  94. with head.config_writer() as writer:
  95. tv = "testopt"
  96. writer.set_value(tv, 1)
  97. assert writer.get_value(tv) == 1
  98. assert head.config_reader().get_value(tv) == 1
  99. with head.config_writer() as writer:
  100. writer.remove_option(tv)
  101. # after the clone, we might still have a tracking branch setup
  102. head.set_tracking_branch(None)
  103. assert head.tracking_branch() is None
  104. remote_ref = rwrepo.remotes[0].refs[0]
  105. assert head.set_tracking_branch(remote_ref) is head
  106. assert head.tracking_branch() == remote_ref
  107. head.set_tracking_branch(None)
  108. assert head.tracking_branch() is None
  109. special_name = 'feature#123'
  110. special_name_remote_ref = SymbolicReference.create(rwrepo, 'refs/remotes/origin/%s' % special_name)
  111. gp_tracking_branch = rwrepo.create_head('gp_tracking#123')
  112. special_name_remote_ref = rwrepo.remotes[0].refs[special_name] # get correct type
  113. gp_tracking_branch.set_tracking_branch(special_name_remote_ref)
  114. assert gp_tracking_branch.tracking_branch().path == special_name_remote_ref.path
  115. git_tracking_branch = rwrepo.create_head('git_tracking#123')
  116. rwrepo.git.branch('-u', special_name_remote_ref.name, git_tracking_branch.name)
  117. assert git_tracking_branch.tracking_branch().name == special_name_remote_ref.name
  118. # END for each head
  119. # verify REFLOG gets altered
  120. head = rwrepo.head
  121. cur_head = head.ref
  122. cur_commit = cur_head.commit
  123. pcommit = cur_head.commit.parents[0].parents[0]
  124. hlog_len = len(head.log())
  125. blog_len = len(cur_head.log())
  126. assert head.set_reference(pcommit, 'detached head') is head
  127. # one new log-entry
  128. thlog = head.log()
  129. assert len(thlog) == hlog_len + 1
  130. assert thlog[-1].oldhexsha == cur_commit.hexsha
  131. assert thlog[-1].newhexsha == pcommit.hexsha
  132. # the ref didn't change though
  133. assert len(cur_head.log()) == blog_len
  134. # head changes once again, cur_head doesn't change
  135. head.set_reference(cur_head, 'reattach head')
  136. assert len(head.log()) == hlog_len + 2
  137. assert len(cur_head.log()) == blog_len
  138. # adjusting the head-ref also adjust the head, so both reflogs are
  139. # altered
  140. cur_head.set_commit(pcommit, 'changing commit')
  141. assert len(cur_head.log()) == blog_len + 1
  142. assert len(head.log()) == hlog_len + 3
  143. # with automatic dereferencing
  144. assert head.set_commit(cur_commit, 'change commit once again') is head
  145. assert len(head.log()) == hlog_len + 4
  146. assert len(cur_head.log()) == blog_len + 2
  147. # a new branch has just a single entry
  148. other_head = Head.create(rwrepo, 'mynewhead', pcommit, logmsg='new head created')
  149. log = other_head.log()
  150. assert len(log) == 1
  151. assert log[0].oldhexsha == pcommit.NULL_HEX_SHA
  152. assert log[0].newhexsha == pcommit.hexsha
  153. def test_refs(self):
  154. types_found = set()
  155. for ref in self.rorepo.refs:
  156. types_found.add(type(ref))
  157. assert len(types_found) >= 3
  158. def test_is_valid(self):
  159. assert not Reference(self.rorepo, 'refs/doesnt/exist').is_valid()
  160. assert self.rorepo.head.is_valid()
  161. assert self.rorepo.head.reference.is_valid()
  162. assert not SymbolicReference(self.rorepo, 'hellothere').is_valid()
  163. def test_orig_head(self):
  164. assert type(self.rorepo.head.orig_head()) == SymbolicReference
  165. @with_rw_repo('0.1.6')
  166. def test_head_checkout_detached_head(self, rw_repo):
  167. res = rw_repo.remotes.origin.refs.master.checkout()
  168. assert isinstance(res, SymbolicReference)
  169. assert res.name == 'HEAD'
  170. @with_rw_repo('0.1.6')
  171. def test_head_reset(self, rw_repo):
  172. cur_head = rw_repo.head
  173. old_head_commit = cur_head.commit
  174. new_head_commit = cur_head.ref.commit.parents[0]
  175. cur_head.reset(new_head_commit, index=True) # index only
  176. assert cur_head.reference.commit == new_head_commit
  177. self.failUnlessRaises(ValueError, cur_head.reset, new_head_commit, index=False, working_tree=True)
  178. new_head_commit = new_head_commit.parents[0]
  179. cur_head.reset(new_head_commit, index=True, working_tree=True) # index + wt
  180. assert cur_head.reference.commit == new_head_commit
  181. # paths - make sure we have something to do
  182. rw_repo.index.reset(old_head_commit.parents[0])
  183. cur_head.reset(cur_head, paths="test")
  184. cur_head.reset(new_head_commit, paths="lib")
  185. # hard resets with paths don't work, its all or nothing
  186. self.failUnlessRaises(GitCommandError, cur_head.reset, new_head_commit, working_tree=True, paths="lib")
  187. # we can do a mixed reset, and then checkout from the index though
  188. cur_head.reset(new_head_commit)
  189. rw_repo.index.checkout(["lib"], force=True)
  190. # now that we have a write write repo, change the HEAD reference - its
  191. # like git-reset --soft
  192. heads = rw_repo.heads
  193. assert heads
  194. for head in heads:
  195. cur_head.reference = head
  196. assert cur_head.reference == head
  197. assert isinstance(cur_head.reference, Head)
  198. assert cur_head.commit == head.commit
  199. assert not cur_head.is_detached
  200. # END for each head
  201. # detach
  202. active_head = heads[0]
  203. curhead_commit = active_head.commit
  204. cur_head.reference = curhead_commit
  205. assert cur_head.commit == curhead_commit
  206. assert cur_head.is_detached
  207. self.failUnlessRaises(TypeError, getattr, cur_head, "reference")
  208. # tags are references, hence we can point to them
  209. some_tag = rw_repo.tags[0]
  210. cur_head.reference = some_tag
  211. assert not cur_head.is_detached
  212. assert cur_head.commit == some_tag.commit
  213. assert isinstance(cur_head.reference, TagReference)
  214. # put HEAD back to a real head, otherwise everything else fails
  215. cur_head.reference = active_head
  216. # type check
  217. self.failUnlessRaises(ValueError, setattr, cur_head, "reference", "that")
  218. # head handling
  219. commit = 'HEAD'
  220. prev_head_commit = cur_head.commit
  221. for count, new_name in enumerate(("my_new_head", "feature/feature1")):
  222. actual_commit = commit + "^" * count
  223. new_head = Head.create(rw_repo, new_name, actual_commit)
  224. assert new_head.is_detached
  225. assert cur_head.commit == prev_head_commit
  226. assert isinstance(new_head, Head)
  227. # already exists, but has the same value, so its fine
  228. Head.create(rw_repo, new_name, new_head.commit)
  229. # its not fine with a different value
  230. self.failUnlessRaises(OSError, Head.create, rw_repo, new_name, new_head.commit.parents[0])
  231. # force it
  232. new_head = Head.create(rw_repo, new_name, actual_commit, force=True)
  233. old_path = new_head.path
  234. old_name = new_head.name
  235. assert new_head.rename("hello").name == "hello"
  236. assert new_head.rename("hello/world").name == "hello/world"
  237. assert new_head.rename(old_name).name == old_name and new_head.path == old_path
  238. # rename with force
  239. tmp_head = Head.create(rw_repo, "tmphead")
  240. self.failUnlessRaises(GitCommandError, tmp_head.rename, new_head)
  241. tmp_head.rename(new_head, force=True)
  242. assert tmp_head == new_head and tmp_head.object == new_head.object
  243. logfile = RefLog.path(tmp_head)
  244. assert osp.isfile(logfile)
  245. Head.delete(rw_repo, tmp_head)
  246. # deletion removes the log as well
  247. assert not osp.isfile(logfile)
  248. heads = rw_repo.heads
  249. assert tmp_head not in heads and new_head not in heads
  250. # force on deletion testing would be missing here, code looks okay though ;)
  251. # END for each new head name
  252. self.failUnlessRaises(TypeError, RemoteReference.create, rw_repo, "some_name")
  253. # tag ref
  254. tag_name = "5.0.2"
  255. TagReference.create(rw_repo, tag_name)
  256. self.failUnlessRaises(GitCommandError, TagReference.create, rw_repo, tag_name)
  257. light_tag = TagReference.create(rw_repo, tag_name, "HEAD~1", force=True)
  258. assert isinstance(light_tag, TagReference)
  259. assert light_tag.name == tag_name
  260. assert light_tag.commit == cur_head.commit.parents[0]
  261. assert light_tag.tag is None
  262. # tag with tag object
  263. other_tag_name = "releases/1.0.2RC"
  264. msg = "my mighty tag\nsecond line"
  265. obj_tag = TagReference.create(rw_repo, other_tag_name, message=msg)
  266. assert isinstance(obj_tag, TagReference)
  267. assert obj_tag.name == other_tag_name
  268. assert obj_tag.commit == cur_head.commit
  269. assert obj_tag.tag is not None
  270. TagReference.delete(rw_repo, light_tag, obj_tag)
  271. tags = rw_repo.tags
  272. assert light_tag not in tags and obj_tag not in tags
  273. # remote deletion
  274. remote_refs_so_far = 0
  275. remotes = rw_repo.remotes
  276. assert remotes
  277. for remote in remotes:
  278. refs = remote.refs
  279. # If a HEAD exists, it must be deleted first. Otherwise it might
  280. # end up pointing to an invalid ref it the ref was deleted before.
  281. remote_head_name = "HEAD"
  282. if remote_head_name in refs:
  283. RemoteReference.delete(rw_repo, refs[remote_head_name])
  284. del(refs[remote_head_name])
  285. # END handle HEAD deletion
  286. RemoteReference.delete(rw_repo, *refs)
  287. remote_refs_so_far += len(refs)
  288. for ref in refs:
  289. assert ref.remote_name == remote.name
  290. # END for each ref to delete
  291. assert remote_refs_so_far
  292. for remote in remotes:
  293. # remotes without references should produce an empty list
  294. self.assertEqual(remote.refs, [])
  295. # END for each remote
  296. # change where the active head points to
  297. if cur_head.is_detached:
  298. cur_head.reference = rw_repo.heads[0]
  299. head = cur_head.reference
  300. old_commit = head.commit
  301. head.commit = old_commit.parents[0]
  302. assert head.commit == old_commit.parents[0]
  303. assert head.commit == cur_head.commit
  304. head.commit = old_commit
  305. # setting a non-commit as commit fails, but succeeds as object
  306. head_tree = head.commit.tree
  307. self.failUnlessRaises(ValueError, setattr, head, 'commit', head_tree)
  308. assert head.commit == old_commit # and the ref did not change
  309. # we allow heds to point to any object
  310. head.object = head_tree
  311. assert head.object == head_tree
  312. # cannot query tree as commit
  313. self.failUnlessRaises(TypeError, getattr, head, 'commit')
  314. # set the commit directly using the head. This would never detach the head
  315. assert not cur_head.is_detached
  316. head.object = old_commit
  317. cur_head.reference = head.commit
  318. assert cur_head.is_detached
  319. parent_commit = head.commit.parents[0]
  320. assert cur_head.is_detached
  321. cur_head.commit = parent_commit
  322. assert cur_head.is_detached and cur_head.commit == parent_commit
  323. cur_head.reference = head
  324. assert not cur_head.is_detached
  325. cur_head.commit = parent_commit
  326. assert not cur_head.is_detached
  327. assert head.commit == parent_commit
  328. # test checkout
  329. active_branch = rw_repo.active_branch
  330. for head in rw_repo.heads:
  331. checked_out_head = head.checkout()
  332. assert checked_out_head == head
  333. # END for each head to checkout
  334. # checkout with branch creation
  335. new_head = active_branch.checkout(b="new_head")
  336. assert active_branch != rw_repo.active_branch
  337. assert new_head == rw_repo.active_branch
  338. # checkout with force as we have a changed a file
  339. # clear file
  340. open(new_head.commit.tree.blobs[-1].abspath, 'w').close()
  341. assert len(new_head.commit.diff(None))
  342. # create a new branch that is likely to touch the file we changed
  343. far_away_head = rw_repo.create_head("far_head", 'HEAD~100')
  344. self.failUnlessRaises(GitCommandError, far_away_head.checkout)
  345. assert active_branch == active_branch.checkout(force=True)
  346. assert rw_repo.head.reference != far_away_head
  347. # test reference creation
  348. partial_ref = 'sub/ref'
  349. full_ref = 'refs/%s' % partial_ref
  350. ref = Reference.create(rw_repo, partial_ref)
  351. assert ref.path == full_ref
  352. assert ref.object == rw_repo.head.commit
  353. self.failUnlessRaises(OSError, Reference.create, rw_repo, full_ref, 'HEAD~20')
  354. # it works if it is at the same spot though and points to the same reference
  355. assert Reference.create(rw_repo, full_ref, 'HEAD').path == full_ref
  356. Reference.delete(rw_repo, full_ref)
  357. # recreate the reference using a full_ref
  358. ref = Reference.create(rw_repo, full_ref)
  359. assert ref.path == full_ref
  360. assert ref.object == rw_repo.head.commit
  361. # recreate using force
  362. ref = Reference.create(rw_repo, partial_ref, 'HEAD~1', force=True)
  363. assert ref.path == full_ref
  364. assert ref.object == rw_repo.head.commit.parents[0]
  365. # rename it
  366. orig_obj = ref.object
  367. for name in ('refs/absname', 'rela_name', 'feature/rela_name'):
  368. ref_new_name = ref.rename(name)
  369. assert isinstance(ref_new_name, Reference)
  370. assert name in ref_new_name.path
  371. assert ref_new_name.object == orig_obj
  372. assert ref_new_name == ref
  373. # END for each name type
  374. # References that don't exist trigger an error if we want to access them
  375. self.failUnlessRaises(ValueError, getattr, Reference(rw_repo, "refs/doesntexist"), 'commit')
  376. # exists, fail unless we force
  377. ex_ref_path = far_away_head.path
  378. self.failUnlessRaises(OSError, ref.rename, ex_ref_path)
  379. # if it points to the same commit it works
  380. far_away_head.commit = ref.commit
  381. ref.rename(ex_ref_path)
  382. assert ref.path == ex_ref_path and ref.object == orig_obj
  383. assert ref.rename(ref.path).path == ex_ref_path # rename to same name
  384. # create symbolic refs
  385. symref_path = "symrefs/sym"
  386. symref = SymbolicReference.create(rw_repo, symref_path, cur_head.reference)
  387. assert symref.path == symref_path
  388. assert symref.reference == cur_head.reference
  389. self.failUnlessRaises(OSError, SymbolicReference.create, rw_repo, symref_path, cur_head.reference.commit)
  390. # it works if the new ref points to the same reference
  391. SymbolicReference.create(rw_repo, symref.path, symref.reference).path == symref.path # @NoEffect
  392. SymbolicReference.delete(rw_repo, symref)
  393. # would raise if the symref wouldn't have been deletedpbl
  394. symref = SymbolicReference.create(rw_repo, symref_path, cur_head.reference)
  395. # test symbolic references which are not at default locations like HEAD
  396. # or FETCH_HEAD - they may also be at spots in refs of course
  397. symbol_ref_path = "refs/symbol_ref"
  398. symref = SymbolicReference(rw_repo, symbol_ref_path)
  399. assert symref.path == symbol_ref_path
  400. symbol_ref_abspath = osp.join(rw_repo.git_dir, symref.path)
  401. # set it
  402. symref.reference = new_head
  403. assert symref.reference == new_head
  404. assert osp.isfile(symbol_ref_abspath)
  405. assert symref.commit == new_head.commit
  406. for name in ('absname', 'folder/rela_name'):
  407. symref_new_name = symref.rename(name)
  408. assert isinstance(symref_new_name, SymbolicReference)
  409. assert name in symref_new_name.path
  410. assert symref_new_name.reference == new_head
  411. assert symref_new_name == symref
  412. assert not symref.is_detached
  413. # END for each ref
  414. # create a new non-head ref just to be sure we handle it even if packed
  415. Reference.create(rw_repo, full_ref)
  416. # test ref listing - assure we have packed refs
  417. rw_repo.git.pack_refs(all=True, prune=True)
  418. heads = rw_repo.heads
  419. assert heads
  420. assert new_head in heads
  421. assert active_branch in heads
  422. assert rw_repo.tags
  423. # we should be able to iterate all symbolic refs as well - in that case
  424. # we should expect only symbolic references to be returned
  425. for symref in SymbolicReference.iter_items(rw_repo):
  426. assert not symref.is_detached
  427. # when iterating references, we can get references and symrefs
  428. # when deleting all refs, I'd expect them to be gone ! Even from
  429. # the packed ones
  430. # For this to work, we must not be on any branch
  431. rw_repo.head.reference = rw_repo.head.commit
  432. deleted_refs = set()
  433. for ref in Reference.iter_items(rw_repo):
  434. if ref.is_detached:
  435. ref.delete(rw_repo, ref)
  436. deleted_refs.add(ref)
  437. # END delete ref
  438. # END for each ref to iterate and to delete
  439. assert deleted_refs
  440. for ref in Reference.iter_items(rw_repo):
  441. if ref.is_detached:
  442. assert ref not in deleted_refs
  443. # END for each ref
  444. # reattach head - head will not be returned if it is not a symbolic
  445. # ref
  446. rw_repo.head.reference = Head.create(rw_repo, "master")
  447. # At least the head should still exist
  448. assert osp.isfile(osp.join(rw_repo.git_dir, 'HEAD'))
  449. refs = list(SymbolicReference.iter_items(rw_repo))
  450. assert len(refs) == 1
  451. # test creation of new refs from scratch
  452. for path in ("basename", "dir/somename", "dir2/subdir/basename"):
  453. # REFERENCES
  454. ############
  455. fpath = Reference.to_full_path(path)
  456. ref_fp = Reference.from_path(rw_repo, fpath)
  457. assert not ref_fp.is_valid()
  458. ref = Reference(rw_repo, fpath)
  459. assert ref == ref_fp
  460. # can be created by assigning a commit
  461. ref.commit = rw_repo.head.commit
  462. assert ref.is_valid()
  463. # if the assignment raises, the ref doesn't exist
  464. Reference.delete(ref.repo, ref.path)
  465. assert not ref.is_valid()
  466. self.failUnlessRaises(ValueError, setattr, ref, 'commit', "nonsense")
  467. assert not ref.is_valid()
  468. # I am sure I had my reason to make it a class method at first, but
  469. # now it doesn't make so much sense anymore, want an instance method as well
  470. # See http://byronimo.lighthouseapp.com/projects/51787-gitpython/tickets/27
  471. Reference.delete(ref.repo, ref.path)
  472. assert not ref.is_valid()
  473. ref.object = rw_repo.head.commit
  474. assert ref.is_valid()
  475. Reference.delete(ref.repo, ref.path)
  476. assert not ref.is_valid()
  477. self.failUnlessRaises(ValueError, setattr, ref, 'object', "nonsense")
  478. assert not ref.is_valid()
  479. # END for each path
  480. def test_dereference_recursive(self):
  481. # for now, just test the HEAD
  482. assert SymbolicReference.dereference_recursive(self.rorepo, 'HEAD')
  483. def test_reflog(self):
  484. assert isinstance(self.rorepo.heads.master.log(), RefLog)