def create(klass, path, description=None, bare=False): """ Create a repository at path @param path: where to create the repository @type path: C{str} @param bare: whether to create a bare repository @type bare: C{bool} @return: git repository object @rtype: L{GitRepository} """ args = GitArgs() abspath = os.path.abspath(path) args.add_true(bare, '--bare') git_dir = '' if bare else '.git' try: if not os.path.exists(abspath): os.makedirs(abspath) try: GitCommand("init", args.args, cwd=abspath)() except CommandExecFailed as excobj: raise GitRepositoryError("Error running git init: %s" % excobj) if description: with file(os.path.join(abspath, git_dir, "description"), 'w') as f: description += '\n' if description[-1] != '\n' else '' f.write(description) return klass(abspath) except OSError as err: raise GitRepositoryError("Cannot create Git repository at '%s': %s" % (abspath, err[1])) return None
def is_clean(self, ignore_untracked=False): """ Does the repository contain any uncommitted modifications? @param ignore_untracked: whether to ignore untracked files when checking the repository status @type ignore_untracked: C{bool} @return: C{True} if the repository is clean, C{False} otherwise and Git's status message @rtype: C{tuple} """ if self.bare: return (True, '') clean_msg = 'nothing to commit' args = GitArgs() args.add_true(ignore_untracked, '-uno') out, ret = self._git_getoutput('status', args.args, extra_env={'LC_ALL': 'C'}) if ret: raise GbpError("Can't get repository status") ret = False for line in out: if line.startswith('#'): continue if line.startswith(clean_msg): ret = True break return (ret, "".join(out))
def list_tree(self, treeish, recurse=False): """ Get a trees content. It returns a list of objects that match the 'ls-tree' output: [ mode, type, sha1, path ]. @param treeish: the treeish object to list @type treeish: C{str} @param recurse: whether to list the tree recursively @type recurse: C{bool} @return: the tree @rtype: C{list} of objects. See above. """ args = GitArgs('-z') args.add_true(recurse, '-r') args.add(treeish) out, err, ret = self._git_inout('ls-tree', args.args, capture_stderr=True) if ret: raise GitRepositoryError("Failed to ls-tree '%s': '%s'" % (treeish, err)) tree = [] for line in out.split('\0'): if line: tree.append(line.split(None, 3)) return tree
def push(self, repo=None, src=None, dst=None, ff_only=True): """ Push changes to the remote repo @param repo: repository to push to @type repo: C{str} @param src: the source ref to push @type src: C{str} @param dst: the name of the destination ref to push to @type dst: C{str} @param ff_only: only push if it's a fast forward update @type ff_only: C{bool} """ args = GitArgs() args.add_cond(repo, repo) # Allow for src == '' to delete dst on the remote if src != None: refspec = src if dst: refspec += ':%s' % dst if not ff_only: refspec = '+%s' % refspec args.add(refspec) self._git_command("push", args.args)
def commit_all(self, msg, author_info=None, edit=False): """ Commit all changes to the repository @param msg: commit message @type msg: C{str} @param author_info: authorship information @type author_info: L{GitModifier} """ args = GitArgs('-a') args.add_true(edit, '--edit') self._commit(msg=msg, args=args.args, author_info=author_info)
def create_branch(self, branch, rev=None): """ Create a new branch @param branch: the branch's name @param rev: where to start the branch from If rev is None the branch starts form the current HEAD. """ args = GitArgs(branch) args.add_true(rev, rev) self._git_command("branch", args.args)
def commit_staged(self, msg, author_info=None, edit=False): """ Commit currently staged files to the repository @param msg: commit message @type msg: C{str} @param author_info: authorship information @type author_info: L{GitModifier} @param edit: whether to spawn an editor to edit the commit info @type edit: C{bool} """ args = GitArgs() args.add_true(edit, '--edit') self._commit(msg=msg, args=args.args, author_info=author_info)
def delete_branch(self, branch, remote=False): """ Delete branch I{branch} @param branch: name of the branch to delete @type branch: C{str} @param remote: delete a remote branch @param remote: C{bool} """ args = GitArgs('-D') args.add_true(remote, '-r') args.add(branch) if self.branch != branch: self._git_command("branch", args.args) else: raise GitRepositoryError("Can't delete the branch you're on")
def rev_parse(self, name, short=0): """ Find the SHA1 of a given name @param name: the name to look for @type name: C{str} @param short: try to abbreviate SHA1 to given length @type short: C{int} @return: the name's sha1 @rtype: C{str} """ args = GitArgs("--quiet", "--verify") args.add_cond(short, '--short=%d' % short) args.add(name) sha, ret = self._git_getoutput('rev-parse', args.args) if ret: raise GitRepositoryError("revision '%s' not found" % name) return sha[0].strip()
def get_merge_base(self, commit1, commit2): """ Get the common ancestor between two commits @param commit1: commit SHA1 or name of a branch or tag @type commit1: C{str} @param commit2: commit SHA1 or name of a branch or tag @type commit2: C{str} @return: SHA1 of the common ancestor @rtype: C{str} """ args = GitArgs() args.add(commit1) args.add(commit2) sha1, stderr, ret = self._git_inout('merge-base', args.args, capture_stderr=True) if not ret: return sha1.strip() else: raise GitRepositoryError("Failed to get common ancestor: %s" % stderr.strip())
def get_upstream_branch(self, local_branch): """ Get upstream branch for the local branch @param local_branch: name fo the local branch @type local_branch: C{str} @return: upstream (remote/branch) or '' if no upstream found @rtype: C{str} """ args = GitArgs('--format=%(upstream:short)') if self.has_branch(local_branch, remote=False): args.add('refs/heads/%s' % local_branch) else: raise GitRepositoryError("Branch %s doesn't exist!" % local_branch) out = self._git_getoutput('for-each-ref', args.args)[0] return out[0].strip()
def write_file(self, filename, filters=True): """ Hash a single file and write it into the object database @param filename: the filename to the content of the file to hash @type filename: C{str} @param filters: whether to run filters @type filters: C{bool} @return: the hash of the file @rtype: C{str} """ args = GitArgs('-w', '-t', 'blob') args.add_false(filters, '--no-filters') args.add(filename) sha1, stderr, ret = self._git_inout('hash-object', args.args, capture_stderr=True) if not ret: return sha1.strip() else: raise GbpError("Failed to hash %s: %s" % (filename, stderr))
def format_patches(self, start, end, output_dir, signature=True, thread=None): """ Output the commits between start and end as patches in output_dir """ options = GitArgs('-N', '-k', '-o', output_dir) options.add_cond(not signature, '--no-signature') options.add('%s...%s' % (start, end)) options.add_cond(thread, '--thread=%s' % thread, '--no-thread') output, ret = self._git_getoutput('format-patch', options.args) return [ line.strip() for line in output ]
def merge(self, commit, verbose=False, edit=False): """ Merge changes from the named commit into the current branch @param commit: the commit to merge from (usually a branch name or tag) @type commit: C{str} @param verbose: whether to print a summary after the merge @type verbose: C{bool} @param edit: wheter to invoke an editor to edit the merge message @type edit: C{bool} """ args = GitArgs() args.add_cond(verbose, '--summary', '--no-summary') args.add_cond(edit, '--edit', '--no-edit') args.add(commit) self._git_command("merge", args.args)
def fetch(self, repo=None, tags=False, depth=0): """ Download objects and refs from another repository. @param repo: repository to fetch from @type repo: C{str} @param tags: whether to fetch all tag objects @type tags: C{bool} @param depth: deepen the history of (shallow) repository to depth I{depth} @type depth: C{int} """ args = GitArgs('--quiet') args.add_true(tags, '--tags') args.add_cond(depth, '--depth=%s' % depth) args.add_cond(repo, repo) self._git_command("fetch", args.args)
def add_remote_repo(self, name, url, tags=True, fetch=False): """ Add a tracked remote repository @param name: the name to use for the remote @type name: C{str} @param url: the url to add @type url: C{str} @param tags: whether to fetch tags @type tags: C{bool} @param fetch: whether to fetch immediately from the remote side @type fetch: C{bool} """ args = GitArgs('add') args.add_false(tags, '--no-tags') args.add_true(fetch, '--fetch') args.add(name, url) self._git_command("remote", args.args)
def branch_contains(self, branch, commit, remote=False): """ Check if branch I{branch} contains commit I{commit} @param branch: the branch the commit should be on @type branch: C{str} @param commit: the C{str} commit to check @type commit: C{str} @param remote: whether to check remote instead of local branches @type remote: C{bool} """ args = GitArgs() args.add_true(remote, '-r') args.add('--contains') args.add(commit) out, ret = self._git_getoutput('branch', args.args) for line in out: # remove prefix '*' for current branch before comparing line = line.replace('*', '') if line.strip() == branch: return True return False
def get_commits(self, since=None, until=None, paths=None, num=0, first_parent=False, options=None): """ Get commits from since to until touching paths @param since: commit to start from @type since: C{str} @param until: last commit to get @type until: C{str} @param paths: only list commits touching paths @type paths: C{list} of C{str} @param num: maximum number of commits to fetch @type num: C{int} @param options: list of additional options passed to git log @type options: C{list} of C{str}ings @param first_parent: only follow first parent when seeing a merge commit @type first_parent: C{bool} """ args = GitArgs('--pretty=format:%H') args.add_true(num, '-%d' % num) args.add_true(first_parent, '--first-parent') args.add_true(since and until, '%s..%s' % (since, until)) args.add_cond(options, options) args.add("--") if isinstance(paths, basestring): paths = [ paths ] args.add_cond(paths, paths) commits, ret = self._git_getoutput('log', args.args) if ret: where = " on %s" % paths if paths else "" raise GitRepositoryError("Error getting commits %s..%s%s" % (since, until, where)) return [ commit.strip() for commit in commits ]
def clone(klass, path, remote, depth=0, recursive=False, mirror=False, bare=False, auto_name=True): """ Clone a git repository at I{remote} to I{path}. @param path: where to clone the repository to @type path: C{str} @param remote: URL to clone @type remote: C{str} @param depth: create a shallow clone of depth I{depth} @type depth: C{int} @param recursive: whether to clone submodules @type recursive: C{bool} @param mirror: whether to pass --mirror to git-clone @type mirror: C{bool} @param bare: whether to create a bare repository @type bare: C{bool} @param auto_name: If I{True} create a directory below I{path} based on the I{remote}s name. Otherwise create the repo directly at I{path}. @type auto_name: C{bool} @return: git repository object @rtype: L{GitRepository} """ abspath = os.path.abspath(path) if auto_name: name = None else: abspath, name = abspath.rsplit('/', 1) args = GitArgs('--quiet') args.add_true(depth, '--depth', depth) args.add_true(recursive, '--recursive') args.add_true(mirror, '--mirror') args.add_true(bare, '--bare') args.add(remote) args.add_true(name, name) try: if not os.path.exists(abspath): os.makedirs(abspath) try: GitCommand("clone", args.args, cwd=abspath)() except CommandExecFailed as excobj: raise GitRepositoryError("Error running git clone: %s" % excobj) if not name: name = remote.rstrip('/').rsplit('/',1)[1] if (mirror or bare): if not name.endswith('.git'): name = "%s.git" % name elif name.endswith('.git'): name = name[:-4] return klass(os.path.join(abspath, name)) except OSError as err: raise GitRepositoryError("Cannot clone Git repository " "'%s' to '%s': %s" % (remote, abspath, err[1])) return None