# vim: set fileencoding=utf-8 : # # (C) 2011 Guido Guenther # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """Git fast import class""" import subprocess import time from gbp.errors import GbpError from gbp.format import format_b from gbp.paths import to_bin class FastImport(object): """Add data to a git repository using I{git fast-import}""" _bufsize = 1024 m_regular = 644 m_exec = 755 m_symlink = 120000 def __init__(self, repo): """ @param repo: the git repository L{FastImport} acts on @type repo: L{GitRepository} """ self._repo = repo try: self._fi = subprocess.Popen(['git', 'fast-import', '--quiet'], stdin=subprocess.PIPE, cwd=repo.path) self._out = self._fi.stdin except OSError as err: raise GbpError("Error spawning git fast-import: %s" % err) except ValueError as err: raise GbpError( "Invalid argument when spawning git fast-import: %s" % err) def _do_data(self, fd, size): self._out.write(format_b(b"data %d\n", size)) while True: data = fd.read(self._bufsize) self._out.write(data) if len(data) != self._bufsize: break self._out.write(b"\n") def _do_file(self, filename, mode, fd, size): name = b"/".join(to_bin(filename).split(b'/')[1:]) self._out.write(format_b(b"M %d inline %s\n", mode, name)) self._do_data(fd, size) def add_file(self, filename, fd, size, mode=m_regular): """ Add a file @param filename: the name of the file to add @type filename: C{str} @param fd: stream to read data from @type fd: C{File} like object @param size: size of the file to add @type size: C{int} @param mode: file mode, default is L{FastImport.m_regular}. @type mode: C{int} """ self._do_file(filename, mode, fd, size) def add_symlink(self, linkname, linktarget): """ Add a symlink @param linkname: the symbolic link's name @param linkname: C{str} @param linktarget: the target the symlink points to @type linktarget: C{str} """ linktarget = to_bin(linktarget) linkname = to_bin(linkname) self._out.write(format_b(b"M %d inline %s\n", self.m_symlink, linkname)) self._out.write(format_b(b"data %d\n", len(linktarget))) self._out.write(format_b(b"%s\n", linktarget)) def start_commit(self, branch, committer, msg): """ Start a fast import commit @param branch: branch to commit on @type branch: C{str} @param committer: the committer information @type committer: L{GitModifier} @param msg: the commit message @type msg: C{str} """ length = len(msg) if not committer.date: committer.date = "%d %s" % (time.time(), time.strftime("%z")) if self._repo.has_branch(branch): from_ = "from refs/heads/%(branch)s^0\n" else: from_ = '' s = """commit refs/heads/%(branch)s committer %(name)s <%(email)s> %(time)s data %(length)s %(msg)s%(from)s""" % {'branch': branch, 'name': committer.name, 'email': committer.email, 'time': committer.date, 'length': length, 'msg': msg, 'from': from_} self._out.write(s.encode()) def deleteall(self): """ Issue I{deleteall} to fastimport so we start from a empty tree """ self._out.write(b"deleteall\n") def close(self): """ Close fast-import issuing all pending actions """ if self._out: self._out.close() if self._fi: self._fi.wait() def __del__(self): self.close()