1 # Copyright (C) 2007, Thomas Leonard
2 # See the README file for details, or visit http://0install.net.
4 import os
, subprocess
, tempfile
5 from zeroinstall
import SafeException
6 from logging
import info
, warn
7 from support
import unpack_tarball
10 def __init__(self
, root_dir
, options
):
11 self
.options
= options
12 self
.root_dir
= root_dir
13 assert type(root_dir
) == str, root_dir
16 def _run(self
, args
, **kwargs
):
17 info("Running git %s (in %s)", ' '.join(args
), self
.root_dir
)
18 return subprocess
.Popen(["git"] + args
, cwd
= self
.root_dir
, **kwargs
)
20 def _run_check(self
, args
, **kwargs
):
21 child
= self
._run
(args
, **kwargs
)
24 raise SafeException("Git %s failed with exit code %d" % (repr(args
), code
))
26 def _run_stdout(self
, args
, **kwargs
):
27 child
= self
._run
(args
, stdout
= subprocess
.PIPE
, **kwargs
)
28 stdout
, unused
= child
.communicate()
30 raise SafeException('Failed to get current branch! Exit code %d: %s' % (child
.returncode
, stdout
))
33 def ensure_versioned(self
, path
):
34 """Ensure path is a file tracked by the version control system.
35 @raise SafeException: if file is not tracked"""
36 out
= self
._run
_stdout
(['ls-tree', 'HEAD', path
]).strip()
38 raise SafeException("File '%s' is not under version control, according to git-ls-tree" % path
)
40 def reset_hard(self
, revision
):
41 self
._run
_check
(['reset', '--hard', revision
])
43 def ensure_committed(self
):
44 child
= self
._run
(["status", "--porcelain", "-uno"], stdout
= subprocess
.PIPE
)
45 stdout
, unused
= child
.communicate()
46 if child
.returncode
== 0:
49 raise SafeException('Uncommitted changes! Use "git-commit -a" to commit them. Changes are:\n' + stdout
)
53 child
= self
._run
(["status", "-a"], stdout
= subprocess
.PIPE
)
54 stdout
, unused
= child
.communicate()
55 if not child
.returncode
:
56 raise SafeException('Uncommitted changes! Use "git-commit -a" to commit them. Changes are:\n' + stdout
)
57 for scm
in self
._submodules
():
58 scm
.ensure_committed()
60 def _submodules(self
):
61 for line
in self
._run
_stdout
(['submodule', 'status']).split('\n'):
63 r
, subdir
= line
.strip().split(' ')[:2]
64 scm
= GIT(os
.path
.join(self
.root_dir
, subdir
), self
.options
)
69 def make_tag(self
, version
):
72 def tag(self
, version
, revision
):
73 tag
= self
.make_tag(version
)
75 key_opts
= ['-u', self
.options
.key
]
78 self
._run
_check
(['tag', '-s'] + key_opts
+ ['-m', 'Release %s' % version
, tag
, revision
])
79 print "Tagged as %s" % tag
81 def get_current_branch(self
):
82 current_branch
= self
._run
_stdout
(['symbolic-ref', 'HEAD']).strip()
83 info("Current branch is %s", current_branch
)
86 def get_tagged_versions(self
):
87 child
= self
._run
(['tag', '-l', 'v*'], stdout
= subprocess
.PIPE
)
88 stdout
, unused
= child
.communicate()
91 raise SafeException("git tag failed with exit code %d" % status
)
92 return [v
[1:] for v
in stdout
.split('\n') if v
]
94 def delete_branch(self
, branch
):
95 self
._run
_check
(['branch', '-D', branch
])
97 def push_head_and_release(self
, version
):
98 self
._run
_check
(['push', self
.options
.public_scm_repository
, self
.make_tag(version
), self
.get_current_branch()])
100 def ensure_no_tag(self
, version
):
101 tag
= self
.make_tag(version
)
102 child
= self
._run
(['tag', '-l', tag
], stdout
= subprocess
.PIPE
)
103 stdout
, unused
= child
.communicate()
104 if tag
in stdout
.split('\n'):
105 raise SafeException(("Release %s is already tagged! If you want to replace it, do\n" +
106 "git tag -d %s") % (version
, tag
))
108 def export(self
, prefix
, archive_file
, revision
):
109 child
= self
._run
(['archive', '--format=tar', '--prefix=' + prefix
+ os
.sep
, revision
], stdout
= subprocess
.PIPE
)
110 subprocess
.check_call(['bzip2', '-'], stdin
= child
.stdout
, stdout
= file(archive_file
, 'w'))
111 status
= child
.wait()
113 if os
.path
.exists(archive_file
):
114 os
.unlink(archive_file
)
115 raise SafeException("git-archive failed with exit code %d" % status
)
117 def export_submodules(self
, target
):
118 # Export all sub-modules under target
120 target
= os
.path
.abspath(target
)
121 for scm
in self
._submodules
():
122 tmp
= tempfile
.NamedTemporaryFile(prefix
= '0release-')
124 scm
.export(prefix
= '.', archive_file
= tmp
.name
, revision
= scm
.rev
)
125 os
.chdir(os
.path
.join(target
, scm
.rel_path
))
126 unpack_tarball(tmp
.name
)
131 def commit(self
, message
, branch
, parent
):
132 self
._run
_check
(['add', '-u']) # Commit all changed tracked files to index
133 tree
= self
._run
_stdout
(['write-tree']).strip()
134 child
= self
._run
(['commit-tree', tree
, '-p', parent
], stdin
= subprocess
.PIPE
, stdout
= subprocess
.PIPE
)
135 stdout
, unused
= child
.communicate(message
)
136 commit
= stdout
.strip()
137 info("Committed as %s", commit
)
138 self
._run
_check
(['branch', '-f', branch
, commit
])
141 def get_head_revision(self
):
142 proc
= self
._run
(['rev-parse', 'HEAD'], stdout
= subprocess
.PIPE
)
143 stdout
, unused
= proc
.communicate()
145 raise Exception("git rev-parse failed with exit code %d" % proc
.returncode
)
146 head
= stdout
.strip()
150 def export_changelog(self
, last_release_version
, head
, stream
):
151 if last_release_version
:
152 self
._run
_check
(['log', 'refs/tags/v' + last_release_version
+ '..' + head
], stdout
= stream
)
154 self
._run
_check
(['log', head
], stdout
= stream
)
156 def grep(self
, pattern
):
157 child
= self
._run
(['grep', pattern
])
159 if child
.returncode
in [0, 1]:
161 warn("git grep returned exit code %d", child
.returncode
)
163 def has_submodules(self
):
164 return os
.path
.isfile(os
.path
.join(self
.root_dir
, '.gitmodules'))
166 def get_scm(local_feed
, options
):
167 start_dir
= os
.path
.dirname(os
.path
.abspath(local_feed
.local_path
))
170 if os
.path
.exists(os
.path
.join(current
, '.git')):
171 return GIT(current
, options
)
172 parent
= os
.path
.dirname(current
)
173 if parent
== current
:
174 raise SafeException("Unable to determine which version control system is being used. Couldn't find .git in %s or any parent directory." % start_dir
)