Fixed bug in error reporting of failing shell commands.
[0release.git] / scm.py
blobccffd4482d190212f19800efa702d24c2001d032
1 # Copyright (C) 2007, Thomas Leonard
2 # See the README file for details, or visit http://0install.net.
4 import os, subprocess
5 from zeroinstall import SafeException
6 from logging import info
8 class SCM:
9 def __init__(self, local_iface, options):
10 self.local_iface = local_iface
11 self.options = options
13 class GIT(SCM):
14 def _run(self, args, **kwargs):
15 info("Running git %s", ' '.join(args))
16 return subprocess.Popen(["git"] + args, cwd = os.path.dirname(self.local_iface.uri), **kwargs)
18 def _run_check(self, args, **kwargs):
19 child = self._run(args, **kwargs)
20 code = child.wait()
21 if code:
22 raise SafeException("Git %s failed with exit code %d" % (repr(args), code))
24 def _run_stdout(self, args, **kwargs):
25 child = self._run(args, stdout = subprocess.PIPE, **kwargs)
26 stdout, unused = child.communicate()
27 if child.returncode:
28 raise SafeException('Failed to get current branch! Exit code %d: %s' % (child.returncode, stdout))
29 return stdout
31 def ensure_versioned(self, path):
32 """Ensure path is a file tracked by the version control system.
33 @raise SafeException: if file is not tracked"""
34 out = self._run_stdout(['ls-tree', 'HEAD', path]).strip()
35 if not out:
36 raise SafeException("File '%s' is not under version control, according to git-ls-tree" % path)
38 def reset_hard(self, revision):
39 self._run_check(['reset', '--hard', revision])
41 def ensure_committed(self):
42 child = self._run(["status", "-a"], stdout = subprocess.PIPE)
43 stdout, unused = child.communicate()
44 if not child.returncode:
45 raise SafeException('Uncommitted changes! Use "git-commit -a" to commit them. Changes are:\n' + stdout)
47 def make_tag(self, version):
48 return 'v' + version
50 def tag(self, version, revision):
51 tag = self.make_tag(version)
52 if self.options.key:
53 key_opts = ['-u', self.options.key]
54 else:
55 key_opts = []
56 self._run_check(['tag', '-s'] + key_opts + ['-m', 'Release %s' % version, tag, revision])
57 print "Tagged as %s" % tag
59 def get_current_branch(self):
60 current_branch = self._run_stdout(['symbolic-ref', 'HEAD']).strip()
61 info("Current branch is %s", current_branch)
62 return current_branch
64 def delete_branch(self, branch):
65 self._run_check(['branch', '-D', branch])
67 def push_head_and_release(self, version):
68 self._run_check(['push', self.options.public_scm_repository, self.make_tag(version), self.get_current_branch()])
70 def ensure_no_tag(self, version):
71 tag = self.make_tag(version)
72 child = self._run(['tag', '-l', '-q', tag])
73 code = child.wait()
74 if code == 0:
75 raise SafeException(("Release %s is already tagged! If you want to replace it, do\n" +
76 "git-tag -d %s") % (version, tag))
78 def export(self, prefix, archive_file, revision):
79 child = self._run(['archive', '--format=tar', '--prefix=' + prefix + '/', revision], stdout = subprocess.PIPE)
80 subprocess.check_call(['bzip2', '-'], stdin = child.stdout, stdout = file(archive_file, 'w'))
81 status = child.wait()
82 if status:
83 if os.path.exists(archive_file):
84 os.unlink(archive_file)
85 raise SafeException("git-archive failed with exit code %d" % status)
87 def commit(self, message, branch, parent):
88 self._run_check(['add', '-u']) # Commit all changed tracked files to index
89 tree = self._run_stdout(['write-tree']).strip()
90 child = self._run(['commit-tree', tree, '-p', parent], stdin = subprocess.PIPE, stdout = subprocess.PIPE)
91 stdout, unused = child.communicate(message)
92 commit = stdout.strip()
93 info("Committed as %s", commit)
94 self._run_check(['branch', '-f', branch, commit])
95 return commit
97 def get_head_revision(self):
98 proc = self._run(['rev-parse', 'HEAD'], stdout = subprocess.PIPE)
99 stdout, unused = proc.communicate()
100 if proc.returncode:
101 raise Exception("git rev-parse failed with exit code %d" % proc.returncode)
102 head = stdout.strip()
103 assert head
104 return head
106 def export_changelog(self, last_release_version, head, stream):
107 if last_release_version:
108 self._run_check(['log', 'refs/tags/v' + last_release_version + '..' + head], stdout = stream)
109 else:
110 self._run_check(['log', head], stdout = stream)