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

"""Utilities to call Git commands.""" 

 

import logging 

import os 

import re 

from contextlib import suppress 

 

from . import common, settings 

from .exceptions import ShellError 

from .shell import call 

 

 

log = logging.getLogger(__name__) 

 

 

def git(*args, **kwargs): 

return call('git', *args, **kwargs) 

 

 

def clone(repo, path, *, cache=settings.CACHE, sparse_paths=None, rev=None): 

"""Clone a new Git repository.""" 

log.debug("Creating a new repository...") 

 

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

if name.endswith(".git"): 

name = name[:-4] 

 

reference = os.path.join(cache, name + ".reference") 

if not os.path.isdir(reference): 

git('clone', '--mirror', repo, reference) 

 

normpath = os.path.normpath(path) 

33 ↛ 34line 33 didn't jump to line 34, because the condition on line 33 was never true if sparse_paths: 

os.mkdir(normpath) 

git('-C', normpath, 'init') 

git('-C', normpath, 'config', 'core.sparseCheckout', 'true') 

git('-C', normpath, 'remote', 'add', '-f', 'origin', reference) 

 

with open("%s/%s/.git/info/sparse-checkout" % 

(os.getcwd(), normpath), 'w') as fd: 

fd.writelines(sparse_paths) 

with open("%s/%s/.git/objects/info/alternates" % 

(os.getcwd(), normpath), 'w') as fd: 

fd.write("%s/objects" % reference) 

 

# We use directly the revision requested here in order to respect, 

# that not all repos have `master` as their default branch 

git('-C', normpath, 'pull', 'origin', rev) 

else: 

git('clone', '--reference', reference, repo, os.path.normpath(path)) 

 

 

def is_sha(rev): 

"""Heuristically determine whether a revision corresponds to a commit SHA. 

 

Any sequence of 7 to 40 hexadecimal digits will be recognized as a 

commit SHA. The minimum of 7 digits is not an arbitrary choice, it 

is the default length for short SHAs in Git. 

""" 

return re.match('^[0-9a-f]{7,40}$', rev) is not None 

 

 

def fetch(repo, rev=None): 

"""Fetch the latest changes from the remote repository.""" 

git('remote', 'set-url', 'origin', repo) 

args = ['fetch', '--tags', '--force', '--prune', 'origin'] 

if rev: 

if is_sha(rev): 

pass # fetch only works with a SHA if already present locally 

elif '@' in rev: 

pass # fetch doesn't work with rev-parse 

else: 

args.append(rev) 

git(*args) 

 

 

def valid(): 

"""Confirm the current directory is a valid working tree.""" 

log.debug("Checking for a valid working tree...") 

 

try: 

git('rev-parse', '--is-inside-work-tree', _show=False) 

except ShellError: 

return False 

else: 

return True 

 

 

def changes(include_untracked=False, display_status=True, _show=False): 

"""Determine if there are changes in the working tree.""" 

status = False 

 

try: 

# Refresh changes 

git('update-index', '-q', '--refresh', _show=False) 

 

# Check for uncommitted changes 

git('diff-index', '--quiet', 'HEAD', _show=_show) 

 

# Check for untracked files 

lines = git('ls-files', '--others', '--exclude-standard', _show=_show) 

 

except ShellError: 

status = True 

 

else: 

status = bool(lines) and include_untracked 

 

if status and display_status: 

with suppress(ShellError): 

lines = git('status', _show=True) 

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

 

return status 

 

 

def update(rev, *, clean=True, fetch=False): # pylint: disable=redefined-outer-name 

"""Update the working tree to the specified revision.""" 

hide = {'_show': False, '_ignore': True} 

 

git('stash', **hide) 

if clean: 

git('clean', '--force', '-d', '-x', _show=False) 

 

rev = _get_sha_from_rev(rev) 

git('checkout', '--force', rev) 

git('branch', '--set-upstream-to', 'origin/' + rev, **hide) 

 

if fetch: 

# if `rev` was a branch it might be tracking something older 

git('pull', '--ff-only', '--no-rebase', **hide) 

 

 

def get_url(): 

"""Get the current repository's URL.""" 

return git('config', '--get', 'remote.origin.url', _show=False)[0] 

 

 

def get_hash(_show=False): 

"""Get the current working tree's hash.""" 

return git('rev-parse', 'HEAD', _show=_show)[0] 

 

 

def get_tag(): 

"""Get the current working tree's tag (if on a tag).""" 

return git('describe', '--tags', '--exact-match', 

_show=False, _ignore=True)[0] 

 

 

def get_branch(): 

"""Get the current working tree's branch.""" 

return git('rev-parse', '--abbrev-ref', 'HEAD', _show=False)[0] 

 

 

def _get_sha_from_rev(rev): 

"""Get a rev-parse string's hash.""" 

if '@{' in rev: # TODO: use regex for this 

parts = rev.split('@') 

branch = parts[0] 

date = parts[1].strip("{}") 

git('checkout', '--force', branch, _show=False) 

rev = git('rev-list', '-n', '1', '--before={!r}'.format(date), 

branch, _show=False)[0] 

return rev