1"""
2hgtools implements several repo managers, each of which provides an interface
3to Mercurial functionality.
4"""
5
6import posixpath
7import itertools
8
9from hgtools import versioning
10from hgtools.util import itersubclasses
11
12class RepoManager(versioning.VersionManagement, object):
13	"""
14	An abstract class defining some interfaces for working with
15	repositories.
16	"""
17	def __init__(self, location='.'):
18		self.location = location
19		self.setup()
20
21	def is_valid(self):
22		"Return True if this is a valid manager for this location."
23		return True
24
25	def setup(self):
26		pass
27
28	@classmethod
29	def get_valid_managers(cls, location):
30		"""
31		Get the valid RepoManagers for this location.
32		"""
33		by_priority_attr = lambda c: getattr(c, 'priority', 0)
34		classes = sorted(itersubclasses(cls), key=by_priority_attr,
35			reverse=True)
36		all_managers = (c(location) for c in classes)
37		return (mgr for mgr in all_managers if mgr.is_valid())
38
39	@staticmethod
40	def get_first_valid_manager(location='.'):
41		try:
42			return next(RepoManager.get_valid_managers(location))
43		except StopIteration as e:
44			e.args = "No source repo or suitable VCS version found",
45			raise
46
47	@staticmethod
48	def existing_only(managers):
49		"""
50		Return only those managers that refer to an existing repo
51		"""
52		return (mgr for mgr in managers if mgr.find_root())
53
54	def __repr__(self):
55		return '{self.__class__.__name__}({self.location})'.format(**vars())
56
57	def find_root(self):
58		raise NotImplementedError()
59
60	def find_files(self):
61		raise NotImplementedError()
62
63	def get_tags(self, rev=None):
64		"""
65		Get the tags for the specified revision (or the current revision
66		if none is specified).
67		"""
68		raise NotImplementedError()
69
70	def get_repo_tags(self):
71		"""
72		Get all tags for the repository.
73		"""
74		raise NotImplementedError()
75
76	def get_parent_tags(self, rev=None):
77		"""
78		Return the tags for the parent revision (or None if no single
79			parent can be identified).
80		"""
81		try:
82			parent_rev = one(self.get_parent_revs(rev))
83		except Exception:
84			return None
85		return self.get_tags(parent_rev)
86
87	def get_parent_revs(self, rev=None):
88		"""
89		Get the parent revision for the specified revision (or the current
90		revision if none is specified).
91		"""
92		raise NotImplementedError
93
94	def is_modified(self):
95		'Does the current working copy have modifications'
96		raise NotImplementedError()
97
98	def find_all_files(self):
99		"""
100		Find files including those in subrepositories.
101		"""
102		files = self.find_files()
103		subrepo_files = (
104			posixpath.join(subrepo.location, filename)
105			for subrepo in self.subrepos()
106			for filename in subrepo.find_files()
107		)
108		return itertools.chain(files, subrepo_files)
109
110	def subrepos(self):
111		try:
112			with open(posixpath.join(self.location, '.hgsub')) as file:
113				subs = list(file)
114		except Exception:
115			subs = []
116
117		locs = [part.partition('=')[0].strip() for part in subs]
118		return [self.__class__(posixpath.join(self.location, loc)) for loc in locs]
119
120# from jaraco.util.itertools
121def one(item):
122	"""
123	Return the first element from the iterable, but raise an exception
124	if elements remain in the iterable after the first.
125
126	>>> one([3])
127	3
128	>>> one(['val', 'other'])
129	Traceback (most recent call last):
130	...
131	ValueError: item contained more than one value
132	>>> one([])
133	Traceback (most recent call last):
134	...
135	StopIteration
136	"""
137	iterable = iter(item)
138	result = next(iterable)
139	if tuple(itertools.islice(iterable, 1)):
140		raise ValueError("item contained more than one value")
141	return result
142