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 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 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 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
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 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 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 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 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