summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGuido Günther <agx@sigxcpu.org>2011-10-27 20:00:37 +0200
committerGuido Günther <agx@sigxcpu.org>2011-10-27 22:03:53 +0200
commitc4f9f23dace4b3ea87955f9ebe093f302b75f810 (patch)
treec2159de2ad4fc3141fccac7532cdedeff023402c
parentfe987441fdbc7f2dc57372df93e4f07c38eb1e23 (diff)
GitRepository: Reorder into groups
-rw-r--r--gbp/git.py1048
1 files changed, 540 insertions, 508 deletions
diff --git a/gbp/git.py b/gbp/git.py
index a14bc86..01cb060 100644
--- a/gbp/git.py
+++ b/gbp/git.py
@@ -59,7 +59,7 @@ class GitModifier(object):
{'GIT_AUTHOR_EMAIL': 'bar', 'GIT_AUTHOR_NAME': 'foo'}
@return: Author information suitable to use as environment variables
- @rtype: dict
+ @rtype: C{dict}
"""
return self._get_env('author')
@@ -72,7 +72,7 @@ class GitModifier(object):
{'GIT_COMMITTER_NAME': 'foo', 'GIT_COMMITTER_EMAIL': 'bar'}
@return: Commiter information suitable to use as environment variables
- @rtype: dict
+ @rtype: C{dict}
"""
return self._get_env('committer')
@@ -82,22 +82,10 @@ class GitRepository(object):
Represents a git repository at I{path}. It's currently assumed that the git
repository is stored in a directory named I{.git/} below I{path}.
- Bare repository aren't currently supported.
-
- @ivar path: The path to the working tree.
- @type path: string
-
- @group Repository Creation: create clone
- @group Branches and Merging: create_branch delete_branch get_branch
- _get_branches get_local_branches get_merge_branch get_remote_branches
- has_branch is_fast_forward merge set_branch
- @group Tags: _build_legacy_tag create_tag delete_tag find_tag get_tags
- has_tag move_tag find_version
- @group Submodules: add_submodule get_submodules has_submodules
- update_submodules
- @group Patches: apply_patch format_patches
- @group Remote Repositories: add_remote_repo get_remote_repos
- has_remote_repo
+ @ivar _path: The path to the working tree
+ @type _path: C{str}
+ @ivar _bare: Whether this is a bare repository
+ @type _bare: C{bool}
"""
def _check_bare(self):
@@ -134,15 +122,15 @@ class GitRepository(object):
Run a git command and return the output
@param command: git command to run
- @type command: string
+ @type command: C{str}
@param args: list of arguments
- @type args: list
+ @type args: C{list}
@param extra_env: extra environment variables to pass
- @type extra_env: dict
+ @type extra_env: C{dict}
@param cwd: directory to swith to when running the command, defaults to I{self.path}
- @type cwd: string
+ @type cwd: C{str}
@return: stdout, return code
- @rtype: tuple
+ @rtype: C{tuple}
"""
output = []
@@ -164,15 +152,15 @@ class GitRepository(object):
Run a git command with input and return output
@param command: git command to run
- @type command: string
+ @type command: C{str}
@param input: input to pipe to command
- @type input: string
+ @type input: C{str}
@param args: list of arguments
- @type args: list
+ @type args: C{list}
@param extra_env: extra environment variables to pass
- @type extra_env: dict
+ @type extra_env: C{dict}
@return: stdout, stderr, return code
- @rtype: tuple
+ @rtype: C{tuple}
"""
env = self.__build_env(extra_env)
cmd = ['git', command] + args
@@ -191,23 +179,22 @@ class GitRepository(object):
at path.
@param command: git command
- @type command: string
+ @type command: C{str}
@param args: command line arguments
- @type args: list
+ @type args: C{list}
@param extra_env: extra environment variables to set when running command
- @type extra_env: dict
+ @type extra_env: C{dict}
"""
GitCommand(command, args, extra_env=extra_env, cwd=self.path)()
@property
def path(self):
+ """The path to the repository"""
return self._path
@property
def base_dir(self):
- """
- Get the base of the repository.
- """
+ """Get the base of the repository"""
return os.path.join(self.path, '.git')
@property
@@ -215,151 +202,54 @@ class GitRepository(object):
"""Wheter this is a bare repository"""
return self._bare
- def has_branch(self, branch, remote=False):
- """
- Check if the repository has branch named I{branch}.
-
- @param branch: branch to look for
- @param remote: only look for remote branches
- @type remote: bool
- @return: C{True} if the repository has this branch, C{False} otherwise
- @rtype: bool
- """
- if remote:
- ref = 'refs/remotes/%s' % branch
- else:
- ref = 'refs/heads/%s' % branch
- failed = self.__git_getoutput('show-ref', [ ref ])[1]
- if failed:
- return False
- return True
-
- def has_treeish(self, treeish):
- """
- Check if the repository has the treeish object I{treeish}.
-
- @param treeish: treeish object to look for
- @type treeish: string
- @return: C{True} if the repository has that tree, C{False} otherwise
- @rtype: bool
- """
-
- out, ret = self.__git_getoutput('ls-tree', [ treeish ])
- return [ True, False ][ret != 0]
-
- def has_tag(self, tag):
- """
- Check if the repository has a tag named I{tag}.
-
- @param tag: tag to look for
- @type tag: string
- @return: C{True} if the repository has that tag, C{False} otherwise
- @rtype: bool
- """
- out, ret = self.__git_getoutput('tag', [ '-l', tag ])
- return [ False, True ][len(out)]
-
-
- def _build_legacy_tag(self, format, version):
- """legacy version numbering"""
- if ':' in version: # strip of any epochs
- version = version.split(':', 1)[1]
- version = version.replace('~', '.')
- return format % dict(version=version)
-
-
- def find_version(self, format, version):
- """
- Check if a certain version is stored in this repo. Return it's SHA1 in
- this case. For legacy tags Don't check only the tag but also the
- message, since the former wasn't injective until recently.
- You only need to use this funciton if you also need to check for legacy
- tags.
-
- @param format: tag pattern
- @param version: debian version number
- @return: sha1 of the version tag
- """
- tag = build_tag(format, version)
- legacy_tag = self._build_legacy_tag(format, version)
- if self.has_tag(tag): # new tags are injective
- return self.rev_parse(tag)
- elif self.has_tag(legacy_tag):
- out, ret = self.__git_getoutput('cat-file', args=['-p', legacy_tag])
- if ret:
- return None
- for line in out:
- if line.endswith(" %s\n" % version):
- return self.rev_parse(legacy_tag)
- elif line.startswith('---'): # GPG signature start
- return None
- return None
-
+ @property
+ def tags(self):
+ """List of all tags in the repository"""
+ return self.get_tags()
- def delete_tag(self, tag):
- """
- Delete a tag named I{tag}
+ @property
+ def branch(self):
+ """The currently checked out branch"""
+ return self.get_branch()
- @param tag: the tag to delete
- @type tag: string
+#{ Branches and Merging
+ def create_branch(self, branch, rev=None):
"""
- if self.has_tag(tag):
- self._git_command("tag", [ "-d", tag ])
+ Create a new branch
- def move_tag(self, old, new):
- self._git_command("tag", [ new, old ])
- self.delete_tag(old)
+ @param branch: the branch's name
+ @param rev: where to start the branch from
- def create_tag(self, name, msg=None, commit=None, sign=False, keyid=None):
+ If rev is None the branch starts form the current HEAD.
"""
- Create a new tag.
+ args = [ branch ]
+ args += [ rev ] if rev else []
- @param name: the tag's name
- @type name: string
- @param msg: The tag message.
- @type msg: string
- @param commit: the commit or object to create the tag at, default
- is I{HEAD}
- @type commit: string
- @param sign: Whether to sing the tag
- @type sign: bool
- @param keyid: the GPG keyid used to sign the tag
- @type keyid: string
- """
- args = []
- args += [ '-m', msg ] if msg else []
- if sign:
- args += [ '-u', keyid ] if keyid else [ '-s' ]
- args += [ name ]
- args += [ commit ] if commit else []
- self._git_command("tag", args)
+ self._git_command("branch", args)
- def get_tags(self, pattern=None):
+ def delete_branch(self, branch, remote=False):
"""
- List tags
+ Delete branch I{branch}
- @param pattern: only list tags matching I{pattern}
- @type pattern: string
+ @param branch: name of the branch to delete
+ @type branch: C{str}
+ @param remote: delete a remote branch
+ @param remote: C{bool}
"""
- args = [ '-l', pattern ] if pattern else []
- return [ line.strip() for line in self.__git_getoutput('tag', args)[0] ]
-
- @property
- def tags(self):
- """List of all tags in the repository"""
- return self.get_tags()
+ args = [ "-D" ]
+ args += [ "-r" ] if remote else []
- @property
- def branch(self):
- """The currently checked out branch"""
- return self.get_branch()
+ if self.branch != branch:
+ self._git_command("branch", args + [branch])
+ else:
+ raise GitRepositoryError, "Can't delete the branch you're on"
def get_branch(self):
"""
On what branch is the current working copy
@return: current branch
- @rtype: string
+ @rtype: C{str}
"""
out, dummy = self.__git_getoutput('symbolic-ref', [ 'HEAD' ])
ref = out[0][:-1]
@@ -368,12 +258,47 @@ class GitRepository(object):
if not failed:
return ref[11:] # strip /refs/heads
+ def has_branch(self, branch, remote=False):
+ """
+ Check if the repository has branch named I{branch}.
+
+ @param branch: branch to look for
+ @param remote: only look for remote branches
+ @type remote: C{bool}
+ @return: C{True} if the repository has this branch, C{False} otherwise
+ @rtype: C{bool}
+ """
+ if remote:
+ ref = 'refs/remotes/%s' % branch
+ else:
+ ref = 'refs/heads/%s' % branch
+ failed = self.__git_getoutput('show-ref', [ ref ])[1]
+ if failed:
+ return False
+ return True
+
+ def set_branch(self, branch):
+ """
+ Switch to branch I{branch}
+
+ @param branch: name of the branch to switch to
+ @type branch: C{str}
+ """
+ if self.branch == branch:
+ return
+
+ if self.bare:
+ self._git_command("symbolic-ref",
+ [ 'HEAD', 'refs/heads/%s' % branch ])
+ else:
+ self._git_command("checkout", [ branch ])
+
def get_merge_branch(self, branch):
"""
Get the branch we'd merge from
@return: repo and branch we would merge from
- @rtype: string
+ @rtype: C{str}
"""
try:
remote = self.get_config("branch.%s.remote" % branch)
@@ -388,7 +313,7 @@ class GitRepository(object):
Merge changes from the named commit into the current branch
@param commit: the commit to merge from (usually a branch name)
- @type commit: string
+ @type commit: C{str}
"""
args = [ "--summary" ] if verbose else [ "--no-summary" ]
self._git_command("merge", args + [ commit ])
@@ -399,7 +324,7 @@ class GitRepository(object):
forward or if the branch is up to date already.
@return: can_fast_forward, up_to_date
- @rtype: tuple
+ @rtype: C{tuple}
"""
has_local = False # local repo has new commits
has_remote = False # remote repo has new commits
@@ -423,51 +348,179 @@ class GitRepository(object):
elif has_remote:
return True, False
- def set_branch(self, branch):
+ def _get_branches(self, remote=False):
"""
- Switch to branch 'branch'
+ Get a list of branches
- @param branch: name of the branch to switch to
+ @param remote: whether to list local or remote branches
+ @type remote: C{bool}
+ @return: local or remote branches
+ @rtype: C{list}
"""
- if self.branch == branch:
- return
+ args = [ '--format=%(refname:short)' ]
+ args += [ 'refs/remotes/' ] if remote else [ 'refs/heads/' ]
+ out = self.__git_getoutput('for-each-ref', args)[0]
+ return [ ref.strip() for ref in out ]
- if self.bare:
- self._git_command("symbolic-ref",
- [ 'HEAD', 'refs/heads/%s' % branch ])
- else:
- self._git_command("checkout", [ branch ])
+ def get_local_branches(self):
+ """
+ Get a list of local branches
- def create_branch(self, branch, rev=None):
+ @return: local branches
+ @rtype: C{list}
"""
- Create a new branch
+ return self._get_branches(remote=False)
- @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.
+ def get_remote_branches(self):
"""
- args = [ branch ]
- args += [ rev ] if rev else []
+ Get a list of remote branches
- self._git_command("branch", args)
+ @return: remote branches
+ @rtype: C{list}
+ """
+ return self._get_branches(remote=True)
- def delete_branch(self, branch, remote=False):
+ def update_ref(self, ref, new, old=None, msg=None):
"""
- Delete branch I{branch}
+ Update ref I{ref} to commit I{new} if I{ref} currently points to
+ I{old}
- @param branch: name of the branch to delete
- @type branch: string
- @param remote: delete a remote branch
- @param remote: bool
+ @param ref: the ref to update
+ @type ref: C{str}
+ @param new: the new value for ref
+ @type new: C{str}
+ @param old: the old value of ref
+ @type old: C{str}
+ @param msg: the reason for the update
+ @type msg: C{str}
"""
- args = [ "-D" ]
- args += [ "-r" ] if remote else []
+ args = [ ref, new ]
+ if old:
+ args += [ old ]
+ if msg:
+ args = [ '-m', msg ] + args
+ self._git_command("update-ref", args)
- if self.branch != branch:
- self._git_command("branch", args + [branch])
- else:
- raise GitRepositoryError, "Can't delete the branch you're on"
+#{ Tags
+
+ def create_tag(self, name, msg=None, commit=None, sign=False, keyid=None):
+ """
+ Create a new tag.
+
+ @param name: the tag's name
+ @type name: C{str}
+ @param msg: The tag message.
+ @type msg: C{str}
+ @param commit: the commit or object to create the tag at, default
+ is I{HEAD}
+ @type commit: C{str}
+ @param sign: Whether to sing the tag
+ @type sign: C{bool}
+ @param keyid: the GPG keyid used to sign the tag
+ @type keyid: C{str}
+ """
+ args = []
+ args += [ '-m', msg ] if msg else []
+ if sign:
+ args += [ '-u', keyid ] if keyid else [ '-s' ]
+ args += [ name ]
+ args += [ commit ] if commit else []
+ self._git_command("tag", args)
+
+ def delete_tag(self, tag):
+ """
+ Delete a tag named I{tag}
+
+ @param tag: the tag to delete
+ @type tag: C{str}
+ """
+ if self.has_tag(tag):
+ self._git_command("tag", [ "-d", tag ])
+
+ def move_tag(self, old, new):
+ self._git_command("tag", [ new, old ])
+ self.delete_tag(old)
+
+ def has_tag(self, tag):
+ """
+ Check if the repository has a tag named I{tag}.
+
+ @param tag: tag to look for
+ @type tag: C{str}
+ @return: C{True} if the repository has that tag, C{False} otherwise
+ @rtype: C{bool}
+ """
+ out, ret = self.__git_getoutput('tag', [ '-l', tag ])
+ return [ False, True ][len(out)]
+
+ def _build_legacy_tag(self, format, version):
+ """legacy version numbering"""
+ if ':' in version: # strip of any epochs
+ version = version.split(':', 1)[1]
+ version = version.replace('~', '.')
+ return format % dict(version=version)
+
+ def find_version(self, format, version):
+ """
+ Check if a certain version is stored in this repo. Return it's SHA1 in
+ this case. For legacy tags Don't check only the tag but also the
+ message, since the former wasn't injective until recently.
+ You only need to use this funciton if you also need to check for legacy
+ tags.
+
+ @param format: tag pattern
+ @param version: debian version number
+ @return: sha1 of the version tag
+ """
+ tag = build_tag(format, version)
+ legacy_tag = self._build_legacy_tag(format, version)
+ if self.has_tag(tag): # new tags are injective
+ return self.rev_parse(tag)
+ elif self.has_tag(legacy_tag):
+ out, ret = self.__git_getoutput('cat-file', args=['-p', legacy_tag])
+ if ret:
+ return None
+ for line in out:
+ if line.endswith(" %s\n" % version):
+ return self.rev_parse(legacy_tag)
+ elif line.startswith('---'): # GPG signature start
+ return None
+ return None
+
+ def find_tag(self, commit, pattern=None):
+ """
+ Find the closest tag to a given commit
+
+ @param commit: the commit to describe
+ @type commit: C{str}
+ @param pattern: only look for tags matching I{pattern}
+ @type pattern: C{str}
+ @return: the found tag
+ @rtype: C{str}
+ """
+ args = [ '--abbrev=0' ]
+ if pattern:
+ args += [ '--match' , pattern ]
+ args += [ commit ]
+
+ tag, ret = self.__git_getoutput('describe', args)
+ if ret:
+ raise GitRepositoryError, "can't find tag for %s" % commit
+ return tag[0].strip()
+
+ def get_tags(self, pattern=None):
+ """
+ List tags
+
+ @param pattern: only list tags matching I{pattern}
+ @type pattern: C{str}
+ @return: tags
+ @rtype: C{list} of C{str}
+ """
+ args = [ '-l', pattern ] if pattern else []
+ return [ line.strip() for line in self.__git_getoutput('tag', args)[0] ]
+#}
def force_head(self, commit, hard=False):
"""
@@ -475,7 +528,7 @@ class GitRepository(object):
@param commit: commit to move HEAD to
@param hard: also update the working copy
- @type hard: bool
+ @type hard: C{bool}
"""
args = ['--quiet']
if hard:
@@ -489,7 +542,7 @@ class GitRepository(object):
@return: C{True} if the repository is clean, C{False} otherwise
and Git's status message
- @rtype: tuple
+ @rtype: C{tuple}
"""
clean_msg = 'nothing to commit'
out = self.__git_getoutput('status')[0]
@@ -508,7 +561,7 @@ class GitRepository(object):
@return: True if the repositorydoesn't have any commits,
False otherwise
- @rtype: bool
+ @rtype: C{bool}
"""
# an empty repo has no branches:
if self.branch:
@@ -516,138 +569,14 @@ class GitRepository(object):
else:
return True
- def list_files(self, types=['cached']):
- """
- List files in index and working tree
-
- @param types: list of types to show
- @type types: list
- """
- all_types = [ 'cached', 'deleted', 'others', 'ignored', 'stage'
- 'unmerged', 'killed', 'modified' ]
- args = [ '-z' ]
-
- for t in types:
- if t in all_types:
- args += [ '--%s' % t ]
- else:
- raise GitRepositoryError("Unknown type '%s'" % t)
- out, ret = self.__git_getoutput('ls-files', args)
- if ret:
- raise GitRepositoryError("Error listing files: '%d'" % ret)
- if out:
- return [ file for file in out[0].split('\0') if file ]
- else:
- return []
-
- def get_commits(self, since=None, until=None, paths=None, options=None,
- first_parent=False):
- """
- Get commits from since to until touching paths
-
- @param since: commit to start from
- @param until: last commit to get
- @param paths: only list commits touching paths
- @param options: list of options passed to git log
- @type options: list of strings
- @param first_parent: only follow first parent when seeing a
- merge commit
- @type first_parent: bool
- """
-
- args = ['--pretty=format:%H']
-
- if options:
- args += options
-
- if first_parent:
- args += [ "--first-parent" ]
-
- if since and until:
- args += ['%s..%s' % (since, until)]
-
- if paths:
- args += [ "--", paths ]
-
- commits, ret = self.__git_getoutput('log', 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 show(self, id):
- """git-show id"""
- commit, ret = self.__git_getoutput('show', [ "--pretty=medium", id ])
- if ret:
- raise GitRepositoryError, "can't get %s" % id
- for line in commit:
- yield line
-
- def grep_log(self, regex, where=None):
- args = ['--pretty=format:%H']
- args.append("--grep=%s" % regex)
- if where:
- args.append(where)
- args.append('--')
-
- commits, ret = self.__git_getoutput('log', args)
- if ret:
- raise GitRepositoryError, "Error grepping log for %s" % regex
- return [ commit.strip() for commit in commits[::-1] ]
-
- def get_subject(self, commit):
- """
- Gets the subject of a commit.
-
- @param commit: the commit to get the subject from
- @return: the commit's subject
- """
- out, ret = self.__git_getoutput('log', ['-n1', '--pretty=format:%s', commit])
- if ret:
- raise GitRepositoryError, "Error getting subject of commit %s" % commit
- return out[0].strip()
-
- def get_commit_info(self, commit):
- """
- Look up data of a specific commit
-
- @param commit: the commit to inspect
- @return: the commit's including id, author, email, subject and body
- @rtype: dict
- """
- out, ret = self.__git_getoutput('log',
- ['--pretty=format:%an%n%ae%n%s%n%b%n',
- '-n1', commit])
- if ret:
- raise GitRepositoryError, "Unable to retrieve log entry for %s" \
- % commit
- return {'id' : commit,
- 'author' : out[0].strip(),
- 'email' : out[1].strip(),
- 'subject' : out[2].rstrip(),
- 'body' : [line.rstrip() for line in out[3:]]}
-
- def find_tag(self, commit, pattern=None):
- "Find the closest tag to a branch's head"
- args = [ '--abbrev=0' ]
- if pattern:
- args += [ '--match' , pattern ]
- args += [ commit ]
-
- tag, ret = self.__git_getoutput('describe', args)
- if ret:
- raise GitRepositoryError, "can't find tag for %s" % commit
- return tag[0].strip()
-
def rev_parse(self, name):
"""
Find the SHA1 of a given name
@param name: the name to look for
- @type name: string
+ @type name: C{str}
@return: the name's sha1
- @rtype: string
+ @rtype: C{str}
"""
args = [ "--quiet", "--verify", name ]
sha, ret = self.__git_getoutput('rev-parse', args)
@@ -655,14 +584,28 @@ class GitRepository(object):
raise GitRepositoryError, "revision '%s' not found" % name
return sha[0].strip()
+#{ Trees
+ def has_treeish(self, treeish):
+ """
+ Check if the repository has the treeish object I{treeish}.
+
+ @param treeish: treeish object to look for
+ @type treeish: C{str}
+ @return: C{True} if the repository has that tree, C{False} otherwise
+ @rtype: C{bool}
+ """
+
+ out, ret = self.__git_getoutput('ls-tree', [ treeish ])
+ return [ True, False ][ret != 0]
+
def write_tree(self, index_file=None):
"""
Create a tree object from the current index
@param index_file: alternate index file to write the current index to
- @type index_file: string
+ @type index_file: C{str}
@return: the new tree object's sha1
- @rtype: string
+ @rtype: C{str}
"""
if index_file:
extra_env = {'GIT_INDEX_FILE': index_file }
@@ -673,112 +616,15 @@ class GitRepository(object):
if ret:
raise GitRepositoryError, "can't write out current index"
return tree[0].strip()
-
- def update_ref(self, ref, new, old=None, msg=None):
- """
- Update ref I{ref} to commit I{new} if I{ref} currently points to
- I{old}
-
- @param ref: the ref to update
- @type ref: string
- @param new: the new value for ref
- @type new: string
- @param old: the old value of ref
- @type old: string
- @param msg: the reason for the update
- @type msg: string
- """
- args = [ ref, new ]
- if old:
- args += [ old ]
- if msg:
- args = [ '-m', msg ] + args
- self._git_command("update-ref", args)
-
- def commit_tree(self, tree, msg, parents, author={}, committer={}):
- """
- Commit a tree with commit msg 'msg' and parents 'parents'
-
- @param tree: tree to commit
- @param msg: commit message
- @param parents: parents of this commit
- @param author: authorship information
- @type author: dict with keys 'name' and 'email'
- @param committer: comitter information
- @type committer: dict with keys 'name' and 'email'
- """
- extra_env = {}
- for key, val in author.items():
- if val:
- extra_env['GIT_AUTHOR_%s' % key.upper()] = val
- for key, val in committer.items():
- if val:
- extra_env['GIT_COMMITTER_%s' % key.upper()] = val
-
- args = [ tree ]
- for parent in parents:
- args += [ '-p' , parent ]
- sha1, stderr, ret = self.__git_inout('commit-tree', args, msg, extra_env)
- if not ret:
- return sha1.strip()
- else:
- raise GbpError, "Failed to commit tree: %s" % stderr
-
- def commit_dir(self, unpack_dir, msg, branch, other_parents=None,
- author={}, committer={}):
- """Replace the current tip of branch 'branch' with the contents from 'unpack_dir'
- @param unpack_dir: content to add
- @type unpack_dir: string
- @param msg: commit message to use
- @type msg: string
- @param branch: branch to add the contents of unpack_dir to
- @type branch: string
- @param other_parents: additional parents of this commit
- @type other_parents: list string
- @param author: author information to use for commit
- @type author: dict with keys 'name', 'email', 'date'
- @param committer: committer information to use for commit
- @type committer: dict with keys 'name', 'email', 'date'"""
-
- git_index_file = os.path.join(self.path, '.git', 'gbp_index')
- try:
- os.unlink(git_index_file)
- except OSError:
- pass
- self.add_files('.', force=True, index_file=git_index_file,
- work_tree=unpack_dir)
- tree = self.write_tree(git_index_file)
-
- if branch:
- cur = self.rev_parse(branch)
- else: # emtpy repo
- cur = None
- branch = 'master'
-
- # Build list of parents:
- parents = []
- if cur:
- parents = [ cur ]
- if other_parents:
- for parent in other_parents:
- sha = self.rev_parse(parent)
- if sha not in parents:
- parents += [ sha ]
-
- commit = self.commit_tree(tree=tree, msg=msg, parents=parents,
- author=author, committer=committer)
- if not commit:
- raise GbpError, "Failed to commit tree"
- self.update_ref("refs/heads/%s" % branch, commit, cur)
- return commit
+#}
def get_config(self, name):
"""
- Gets the config value associated with name
+ Gets the config value associated with I{name}
@param name: config value to get
@return: fetched config value
- @rtype: string
+ @rtype: C{str}
"""
value, ret = self.__git_getoutput('config', [ name ])
if ret: raise KeyError
@@ -790,7 +636,7 @@ class GitRepository(object):
config and environment variables.
@return: name and email
- @rtype: typle
+ @rtype: C{tuple}
"""
try:
name = self.get_config("user.email")
@@ -804,44 +650,14 @@ class GitRepository(object):
name = os.getenv("GIT_AUTHOR_NAME", name)
return (name, email)
- def _get_branches(self, remote=False):
- """
- Get a list of branches
-
- @param remote: whether to list local or remote branches
- @type remote: bool
- @return: local or remote branches
- @rtype: list
- """
- args = [ '--format=%(refname:short)' ]
- args += [ 'refs/remotes/' ] if remote else [ 'refs/heads/' ]
- out = self.__git_getoutput('for-each-ref', args)[0]
- return [ ref.strip() for ref in out ]
-
- def get_remote_branches(self):
- """
- Get a list of remote branches
-
- @return: remote branches
- @rtype: list
- """
- return self._get_branches(remote=True)
-
- def get_local_branches(self):
- """
- Get a list of local branches
-
- @return: local branches
- @rtype: list
- """
- return self._get_branches(remote=False)
+#{ Remote Repositories
def get_remote_repos(self):
"""
Get all remote repositories
@return: remote repositories
- @rtype: list of strings
+ @rtype: C{list} of C{str}
"""
out = self.__git_getoutput('remote')[0]
return [ remote.strip() for remote in out ]
@@ -851,9 +667,9 @@ class GitRepository(object):
Do we know about a remote named I{name}?
@param name: name of the remote repository
- @type name: string
+ @type name: C{str}
@return: C{True} if the remote repositore is known, C{False} otherwise
- @rtype: bool
+ @rtype: C{bool}
"""
if name in self.get_remote_repos():
return True
@@ -865,13 +681,13 @@ class GitRepository(object):
Add a tracked remote repository
@param name: the name to use for the remote
- @type name: string
+ @type name: C{str}
@param url: the url to add
- @type url: string
+ @type url: C{str}
@param tags: whether to fetch tags
- @type tags: bool
+ @type tags: C{bool}
@param fetch: whether to fetch immediately from the remote side
- @type fetch: bool
+ @type fetch: C{bool}
"""
args = [ "add" ]
args += [ '--tags' ] if tags else [ '--no-tags']
@@ -879,14 +695,42 @@ class GitRepository(object):
args += [ name, url ]
self._git_command("remote", args)
+ def fetch(self, repo=None):
+ """
+ Download objects and refs from another repository.
+
+ @param repo: repository to fetch from
+ @type repo: C{str}
+ """
+ if repo:
+ args = [repo]
+
+ self._git_command("fetch", args)
+
+ def pull(self, repo=None, ff_only=False):
+ """
+ Fetch and merge from another repository
+
+ @param repo: repository to fetch from
+ @type repo: C{str}
+ @param ff_only: only merge if this results in a fast forward merge
+ @type ff_only: C{bool}
+ """
+ args = []
+ args += [ '--ff-only' ] if ff_only else []
+ args += [ repo ] if repo else []
+ self._git_command("pull", args)
+
+#{ Files
+
def add_files(self, paths, force=False, index_file=None, work_tree=None):
"""
- Add files to a git repository
+ Add files to a the repository
@param paths: list of files to add
- @type paths: list or string
+ @type paths: list or C{str}
@param force: add files even if they would be ignored by .gitignore
- @type force: bool
+ @type force: C{bool}
@param index_file: alternative index file to use
@param work_tree: alternative working tree to use
"""
@@ -905,15 +749,14 @@ class GitRepository(object):
self._git_command("add", args + paths, extra_env)
-
def remove_files(self, paths, verbose=False):
"""
Remove files from the repository
@param paths: list of files to remove
- @param paths: list or string
+ @param paths: C{list} or C{str}
@param verbose: be verbose
- @type verbose: bool
+ @type verbose: C{bool}
"""
if type(paths) in [type(''), type(u'')]:
paths = [ paths ]
@@ -921,18 +764,44 @@ class GitRepository(object):
args = [] if verbose else ['--quiet']
self._git_command("rm", args + paths)
+ def list_files(self, types=['cached']):
+ """
+ List files in index and working tree
+
+ @param types: list of types to show
+ @type types: C{list}
+ @return: list of files
+ @rtype: C{list} of C{str}
+ """
+ all_types = [ 'cached', 'deleted', 'others', 'ignored', 'stage'
+ 'unmerged', 'killed', 'modified' ]
+ args = [ '-z' ]
+
+ for t in types:
+ if t in all_types:
+ args += [ '--%s' % t ]
+ else:
+ raise GitRepositoryError("Unknown type '%s'" % t)
+ out, ret = self.__git_getoutput('ls-files', args)
+ if ret:
+ raise GitRepositoryError("Error listing files: '%d'" % ret)
+ if out:
+ return [ file for file in out[0].split('\0') if file ]
+ else:
+ return []
+
+#{ Comitting
def _commit(self, msg, args=[], author_info=None):
extra_env = author_info.get_author_env() if author_info else None
self._git_command("commit", ['-q', '-m', msg] + args, extra_env=extra_env)
-
def commit_staged(self, msg, author_info=None):
"""
Commit currently staged files to the repository
@param msg: commit message
- @type msg: string
+ @type msg: C{str}
@param author_info: authorship information
@type author_info: L{GitModifier}
"""
@@ -942,7 +811,7 @@ class GitRepository(object):
"""
Commit all changes to the repository
@param msg: commit message
- @type msg: string
+ @type msg: C{str}
@param author_info: authorship information
@type author_info: L{GitModifier}
"""
@@ -953,9 +822,9 @@ class GitRepository(object):
Commit the given files to the repository
@param files: file or files to commit
- @type files: string or list
+ @type files: C{str} or C{list}
@param msg: commit message
- @type msg: string
+ @type msg: C{str}
@param author_info: authorship information
@type author_info: L{GitModifier}
"""
@@ -963,6 +832,179 @@ class GitRepository(object):
files = [ files ]
self._commit(msg=msg, args=files, author_info=author_info)
+ def commit_dir(self, unpack_dir, msg, branch, other_parents=None,
+ author={}, committer={}):
+ """
+ Replace the current tip of branch 'branch' with the contents from 'unpack_dir'
+
+ @param unpack_dir: content to add
+ @type unpack_dir: C{str}
+ @param msg: commit message to use
+ @type msg: C{str}
+ @param branch: branch to add the contents of unpack_dir to
+ @type branch: C{str}
+ @param other_parents: additional parents of this commit
+ @type other_parents: C{list} of C{str}
+ @param author: author information to use for commit
+ @type author: C{dict} with keys I{name}, I{email}, ${date}
+ @param committer: committer information to use for commit
+ @type committer: C{dict} with keys I{name}, I{email}, I{date}
+ """
+
+ git_index_file = os.path.join(self.path, '.git', 'gbp_index')
+ try:
+ os.unlink(git_index_file)
+ except OSError:
+ pass
+ self.add_files('.', force=True, index_file=git_index_file,
+ work_tree=unpack_dir)
+ tree = self.write_tree(git_index_file)
+
+ if branch:
+ cur = self.rev_parse(branch)
+ else: # emtpy repo
+ cur = None
+ branch = 'master'
+
+ # Build list of parents:
+ parents = []
+ if cur:
+ parents = [ cur ]
+ if other_parents:
+ for parent in other_parents:
+ sha = self.rev_parse(parent)
+ if sha not in parents:
+ parents += [ sha ]
+
+ commit = self.commit_tree(tree=tree, msg=msg, parents=parents,
+ author=author, committer=committer)
+ if not commit:
+ raise GbpError, "Failed to commit tree"
+ self.update_ref("refs/heads/%s" % branch, commit, cur)
+ return commit
+
+ def commit_tree(self, tree, msg, parents, author={}, committer={}):
+ """
+ Commit a tree with commit msg I{msg} and parents I{parents}
+
+ @param tree: tree to commit
+ @param msg: commit message
+ @param parents: parents of this commit
+ @param author: authorship information
+ @type author: C{dict} with keys 'name' and 'email'
+ @param committer: comitter information
+ @type committer: C{dict} with keys 'name' and 'email'
+ """
+ extra_env = {}
+ for key, val in author.items():
+ if val:
+ extra_env['GIT_AUTHOR_%s' % key.upper()] = val
+ for key, val in committer.items():
+ if val:
+ extra_env['GIT_COMMITTER_%s' % key.upper()] = val
+
+ args = [ tree ]
+ for parent in parents:
+ args += [ '-p' , parent ]
+ sha1, stderr, ret = self.__git_inout('commit-tree', args, msg, extra_env)
+ if not ret:
+ return sha1.strip()
+ else:
+ raise GbpError, "Failed to commit tree: %s" % stderr
+
+#{ Commit Information
+
+ def get_commits(self, since=None, until=None, paths=None, options=None,
+ first_parent=False):
+ """
+ Get commits from since to until touching paths
+
+ @param since: commit to start from
+ @param until: last commit to get
+ @param paths: only list commits touching paths
+ @param options: list of 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 = ['--pretty=format:%H']
+
+ if options:
+ args += options
+
+ if first_parent:
+ args += [ "--first-parent" ]
+
+ if since and until:
+ args += ['%s..%s' % (since, until)]
+
+ if paths:
+ args += [ "--", paths ]
+
+ commits, ret = self.__git_getoutput('log', 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 show(self, id):
+ """git-show id"""
+ commit, ret = self.__git_getoutput('show', [ "--pretty=medium", id ])
+ if ret:
+ raise GitRepositoryError, "can't get %s" % id
+ for line in commit:
+ yield line
+
+ def grep_log(self, regex, where=None):
+ args = ['--pretty=format:%H']
+ args.append("--grep=%s" % regex)
+ if where:
+ args.append(where)
+ args.append('--')
+
+ commits, ret = self.__git_getoutput('log', args)
+ if ret:
+ raise GitRepositoryError, "Error grepping log for %s" % regex
+ return [ commit.strip() for commit in commits[::-1] ]
+
+ def get_subject(self, commit):
+ """
+ Gets the subject of a commit.
+
+ @param commit: the commit to get the subject from
+ @return: the commit's subject
+ @rtype: C{str}
+ """
+ out, ret = self.__git_getoutput('log', ['-n1', '--pretty=format:%s', commit])
+ if ret:
+ raise GitRepositoryError, "Error getting subject of commit %s" % commit
+ return out[0].strip()
+
+ def get_commit_info(self, commit):
+ """
+ Look up data of a specific commit
+
+ @param commit: the commit to inspect
+ @return: the commit's including id, author, email, subject and body
+ @rtype: dict
+ """
+ out, ret = self.__git_getoutput('log',
+ ['--pretty=format:%an%n%ae%n%s%n%b%n',
+ '-n1', commit])
+ if ret:
+ raise GitRepositoryError, "Unable to retrieve log entry for %s" \
+ % commit
+ return {'id' : commit,
+ 'author' : out[0].strip(),
+ 'email' : out[1].strip(),
+ 'subject' : out[2].rstrip(),
+ 'body' : [line.rstrip() for line in out[3:]]}
+
+
+#{ Patches
def format_patches(self, start, end, output_dir):
"""
Output the commits between start and end as patches in output_dir
@@ -982,6 +1024,7 @@ class GitRepository(object):
args += [ '-p', strip ]
args.append(patch)
self._git_command("apply", args)
+#}
def archive(self, format, prefix, output, treeish, **kwargs):
args = [ '--format=%s' % format, '--prefix=%s' % prefix,
@@ -995,39 +1038,21 @@ class GitRepository(object):
Cleanup unnecessary files and optimize the local repository
param auto: only cleanup if required
- param auto: bool
+ param auto: C{bool}
"""
args = [ '--auto' ] if auto else []
self._git_command("gc", args)
- def fetch(self, repo=None):
- """
- Download objects and refs from another repository.
-
- param repo: repository to fetch from
- type repo: string
- """
- if repo:
- args = [repo]
-
- self._git_command("fetch", args)
+#{ Submodules
- def pull(self, repo=None, ff_only=False):
+ def has_submodules(self):
"""
- Fetch and merge from another repository
+ Does the repo have any submodules?
- @param repo: repository to fetch from
- @type repo: string
- @param ff_only: only merge if this results in a fast forward merge
- @type ff_only: bool
+ @return: C{True} if the repository has any submodules, C{False}
+ otherwise
+ @rtype: C{bool}
"""
- args = []
- args += [ '--ff-only' ] if ff_only else []
- args += [ repo ] if repo else []
- self._git_command("pull", args)
-
- def has_submodules(self):
- """Does the repo have any submodules?"""
if os.path.exists('.gitmodules'):
return True
else:
@@ -1039,6 +1064,7 @@ class GitRepository(object):
Add a submodule
@param repo_path: path to submodule
+ @type repo_path: C{str}
"""
self._git_command("submodule", [ "add", repo_path ])
@@ -1048,8 +1074,11 @@ class GitRepository(object):
Update all submodules
@param init: whether to initialize the submodule if necessary
+ @type init: C{bool}
@param recursive: whether to update submodules recursively
+ @type recursive: C{bool}
@param fetch: whether to fetch new objects
+ @type fetch: C{bool}
"""
if not self.has_submodules():
@@ -1095,15 +1124,17 @@ class GitRepository(object):
recursive=recursive)
return submodules
+#{ Repository Creation
+
@classmethod
def create(klass, path, description=None, bare=False):
"""
Create a repository at path
@param path: where to create the repository
- @type path: string
+ @type path: C{str}
@return: git repository object
- @rtype:GitRepository
+ @rtype: L{GitRepository}
"""
abspath = os.path.abspath(path)
@@ -1127,15 +1158,15 @@ class GitRepository(object):
Clone a git repository at I{remote} to I{path}
@param path: where to clone the repository to
- @type path: string
+ @type path: C{str}
@param remote: URL to clone
- @type remote: string
+ @type remote: C{str}
@param depth: create a shallow clone of depth I{depth}
- @type depth: int
+ @type depth: C{int}
@param recursive: whether to clone submodules
- @type recursive: bool
+ @type recursive: C{bool}
@return: git repository object
- @rtype:GitRepository
+ @rtype: L{GitRepository}
"""
abspath = os.path.abspath(path)
args = [ '--depth', depth ] if depth else []
@@ -1154,6 +1185,7 @@ class GitRepository(object):
except OSError, err:
raise GitRepositoryError, "Cannot clone Git repository %s to %s: %s " % (remote, abspath, err[1])
return None
+#}
class FastImport(object):
@@ -1264,7 +1296,7 @@ def tag_to_version(tag, format):
def rfc822_date_to_git(rfc822_date):
- """Parse a date in RFC822 format, and convert to a 'seconds tz' string.
+ """Parse a date in RFC822 format, and convert to a 'seconds tz' C{str}ing.
>>> rfc822_date_to_git('Thu, 1 Jan 1970 00:00:01 +0000')
'1 +0000'