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

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...") 

72 ↛ 73line 72 didn't jump to line 73, because the condition on line 72 was never true 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...") 

 

92 ↛ 93line 92 didn't jump to line 93, because the condition on line 92 was never true 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) 

115 ↛ 116line 115 didn't jump to line 116, because the condition on line 115 was never true 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.""" 

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): 

151 ↛ 155line 151 didn't jump to line 155, because the condition on line 151 was never false 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 

 

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.""" 

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