Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

"""Wrappers for the dependency configuration files.""" 

 

import os 

import logging 

import warnings 

 

import yorm 

from yorm.types import String, NullableString, List, AttributeDictionary 

 

from .. import common, exceptions, shell, git 

 

 

log = logging.getLogger(__name__) 

 

 

@yorm.attr(name=String) 

@yorm.attr(repo=String) 

@yorm.attr(rev=String) 

@yorm.attr(link=NullableString) 

@yorm.attr(scripts=List.of_type(String)) 

class Source(AttributeDictionary): 

"""A dictionary of `git` and `ln` arguments.""" 

 

DIRTY = '<dirty>' 

UNKNOWN = '<unknown>' 

 

def __init__(self, repo, name=None, rev='master', link=None, scripts=None): 

super().__init__() 

self.repo = repo 

self.name = self._infer_name(repo) if name is None else name 

self.rev = rev 

self.link = link 

self.scripts = scripts or [] 

 

for key in ['name', 'repo', 'rev']: 

if not self[key]: 

msg = "'{}' required for {}".format(key, repr(self)) 

raise exceptions.InvalidConfig(msg) 

 

def __repr__(self): 

return "<source {}>".format(self) 

 

def __str__(self): 

pattern = "'{r}' @ '{v}' in '{d}'" 

if self.link: 

pattern += " <- '{s}'" 

return pattern.format(r=self.repo, v=self.rev, d=self.name, s=self.link) 

 

def __eq__(self, other): 

return self.name == other.name 

 

def __ne__(self, other): 

return self.name != other.name 

 

def __lt__(self, other): 

return self.name < other.name 

 

def update_files(self, force=False, fetch=False, clean=True): 

"""Ensure the source matches the specified revision.""" 

log.info("Updating source files...") 

 

# Clone the repository if needed 

if not os.path.exists(self.name): 

git.clone(self.repo, self.name) 

 

# Enter the working tree 

shell.cd(self.name) 

if not git.valid(): 

raise self._invalid_repository 

 

# Check for uncommitted changes 

if not force: 

log.debug("Confirming there are no uncommitted changes...") 

if git.changes(include_untracked=clean): 

msg = "Uncommitted changes in {}".format(os.getcwd()) 

raise exceptions.UncommittedChanges(msg) 

 

# Fetch the desired revision 

if fetch or self.rev not in (git.get_branch(), 

git.get_hash(), 

git.get_tag()): 

git.fetch(self.repo, self.rev) 

 

# Update the working tree to the desired revision 

git.update(self.rev, fetch=fetch, clean=clean) 

 

def create_link(self, root, force=False): 

"""Create a link from the target name to the current directory.""" 

if not self.link: 

return 

 

log.info("Creating a symbolic link...") 

 

if os.name == 'nt': 

warnings.warn("Symbolic links are not supported on Windows") 

return 

 

target = os.path.join(root, self.link) 

source = os.path.relpath(os.getcwd(), os.path.dirname(target)) 

 

if os.path.islink(target): 

os.remove(target) 

elif os.path.exists(target): 

if force: 

shell.rm(target) 

else: 

msg = "Preexisting link location at {}".format(target) 

raise exceptions.UncommittedChanges(msg) 

 

shell.ln(source, target) 

 

def run_scripts(self, force=False): 

log.info("Running install scripts...") 

 

# Enter the working tree 

shell.cd(self.name) 

if not git.valid(): 

raise self._invalid_repository 

 

# Check for scripts 

if not self.scripts: 

common.show("(no scripts to run)", color='shell_info') 

common.newline() 

return 

 

# Run all scripts 

for script in self.scripts: 

try: 

lines = shell.call(script, _shell=True) 

except exceptions.ShellError as exc: 

common.show(*exc.output, color='shell_error') 

cmd = exc.program 

if force: 

log.debug("Ignored error from call to '%s'", cmd) 

else: 

msg = "Command '{}' failed in {}".format(cmd, os.getcwd()) 

raise exceptions.ScriptFailure(msg) 

else: 

common.show(*lines, color='shell_output') 

common.newline() 

 

def identify(self, allow_dirty=True, allow_missing=True): 

"""Get the path and current repository URL and hash.""" 

144 ↛ 146line 144 didn't jump to line 146, because the condition on line 144 was never true if os.path.isdir(self.name): 

 

shell.cd(self.name) 

if not git.valid(): 

raise self._invalid_repository 

 

path = os.getcwd() 

url = git.get_url() 

if git.changes(display_status=not allow_dirty, _show=True): 

if not allow_dirty: 

msg = "Uncommitted changes in {}".format(os.getcwd()) 

raise exceptions.UncommittedChanges(msg) 

 

common.show(self.DIRTY, color='git_dirty', log=False) 

common.newline() 

return path, url, self.DIRTY 

else: 

rev = git.get_hash(_show=True) 

common.show(rev, color='git_rev', log=False) 

common.newline() 

return path, url, rev 

 

166 ↛ 172line 166 didn't jump to line 172, because the condition on line 166 was never false elif allow_missing: 

 

return os.getcwd(), '<missing>', self.UNKNOWN 

 

else: 

 

raise self._invalid_repository 

 

def lock(self, rev=None): 

"""Return a locked version of the current source.""" 

176 ↛ 178line 176 didn't jump to line 178, because the condition on line 176 was never false if rev is None: 

_, _, rev = self.identify(allow_dirty=False, allow_missing=False) 

source = self.__class__(self.repo, self.name, rev, 

self.link, self.scripts) 

return source 

 

@property 

def _invalid_repository(self): 

path = os.path.join(os.getcwd(), self.name) 

msg = "Not a valid repository: {}".format(path) 

return exceptions.InvalidRepository(msg) 

 

@staticmethod 

def _infer_name(repo): 

filename = repo.split('/')[-1] 

name = filename.split('.')[0] 

return name