##// END OF EJS Templates
Invalidate gits parsed_refs cache after commit, tagging or tag remove
marcink -
r2539:da18c423 beta
parent child Browse files
Show More
@@ -1,194 +1,196 b''
1 import time
1 import time
2 import datetime
2 import datetime
3 import posixpath
3 import posixpath
4 from dulwich import objects
4 from dulwich import objects
5 from dulwich.repo import Repo
5 from dulwich.repo import Repo
6 from rhodecode.lib.vcs.backends.base import BaseInMemoryChangeset
6 from rhodecode.lib.vcs.backends.base import BaseInMemoryChangeset
7 from rhodecode.lib.vcs.exceptions import RepositoryError
7 from rhodecode.lib.vcs.exceptions import RepositoryError
8 from rhodecode.lib.vcs.utils import safe_str
8 from rhodecode.lib.vcs.utils import safe_str
9
9
10
10
11 class GitInMemoryChangeset(BaseInMemoryChangeset):
11 class GitInMemoryChangeset(BaseInMemoryChangeset):
12
12
13 def commit(self, message, author, parents=None, branch=None, date=None,
13 def commit(self, message, author, parents=None, branch=None, date=None,
14 **kwargs):
14 **kwargs):
15 """
15 """
16 Performs in-memory commit (doesn't check workdir in any way) and
16 Performs in-memory commit (doesn't check workdir in any way) and
17 returns newly created ``Changeset``. Updates repository's
17 returns newly created ``Changeset``. Updates repository's
18 ``revisions``.
18 ``revisions``.
19
19
20 :param message: message of the commit
20 :param message: message of the commit
21 :param author: full username, i.e. "Joe Doe <joe.doe@example.com>"
21 :param author: full username, i.e. "Joe Doe <joe.doe@example.com>"
22 :param parents: single parent or sequence of parents from which commit
22 :param parents: single parent or sequence of parents from which commit
23 would be derieved
23 would be derieved
24 :param date: ``datetime.datetime`` instance. Defaults to
24 :param date: ``datetime.datetime`` instance. Defaults to
25 ``datetime.datetime.now()``.
25 ``datetime.datetime.now()``.
26 :param branch: branch name, as string. If none given, default backend's
26 :param branch: branch name, as string. If none given, default backend's
27 branch would be used.
27 branch would be used.
28
28
29 :raises ``CommitError``: if any error occurs while committing
29 :raises ``CommitError``: if any error occurs while committing
30 """
30 """
31 self.check_integrity(parents)
31 self.check_integrity(parents)
32
32
33 from .repository import GitRepository
33 from .repository import GitRepository
34 if branch is None:
34 if branch is None:
35 branch = GitRepository.DEFAULT_BRANCH_NAME
35 branch = GitRepository.DEFAULT_BRANCH_NAME
36
36
37 repo = self.repository._repo
37 repo = self.repository._repo
38 object_store = repo.object_store
38 object_store = repo.object_store
39
39
40 ENCODING = "UTF-8"
40 ENCODING = "UTF-8"
41 DIRMOD = 040000
41 DIRMOD = 040000
42
42
43 # Create tree and populates it with blobs
43 # Create tree and populates it with blobs
44 commit_tree = self.parents[0] and repo[self.parents[0]._commit.tree] or\
44 commit_tree = self.parents[0] and repo[self.parents[0]._commit.tree] or\
45 objects.Tree()
45 objects.Tree()
46 for node in self.added + self.changed:
46 for node in self.added + self.changed:
47 # Compute subdirs if needed
47 # Compute subdirs if needed
48 dirpath, nodename = posixpath.split(node.path)
48 dirpath, nodename = posixpath.split(node.path)
49 dirnames = dirpath and dirpath.split('/') or []
49 dirnames = dirpath and dirpath.split('/') or []
50 parent = commit_tree
50 parent = commit_tree
51 ancestors = [('', parent)]
51 ancestors = [('', parent)]
52
52
53 # Tries to dig for the deepest existing tree
53 # Tries to dig for the deepest existing tree
54 while dirnames:
54 while dirnames:
55 curdir = dirnames.pop(0)
55 curdir = dirnames.pop(0)
56 try:
56 try:
57 dir_id = parent[curdir][1]
57 dir_id = parent[curdir][1]
58 except KeyError:
58 except KeyError:
59 # put curdir back into dirnames and stops
59 # put curdir back into dirnames and stops
60 dirnames.insert(0, curdir)
60 dirnames.insert(0, curdir)
61 break
61 break
62 else:
62 else:
63 # If found, updates parent
63 # If found, updates parent
64 parent = self.repository._repo[dir_id]
64 parent = self.repository._repo[dir_id]
65 ancestors.append((curdir, parent))
65 ancestors.append((curdir, parent))
66 # Now parent is deepest exising tree and we need to create subtrees
66 # Now parent is deepest exising tree and we need to create subtrees
67 # for dirnames (in reverse order) [this only applies for nodes from added]
67 # for dirnames (in reverse order) [this only applies for nodes from added]
68 new_trees = []
68 new_trees = []
69 blob = objects.Blob.from_string(node.content.encode(ENCODING))
69 blob = objects.Blob.from_string(node.content.encode(ENCODING))
70 node_path = node.name.encode(ENCODING)
70 node_path = node.name.encode(ENCODING)
71 if dirnames:
71 if dirnames:
72 # If there are trees which should be created we need to build
72 # If there are trees which should be created we need to build
73 # them now (in reverse order)
73 # them now (in reverse order)
74 reversed_dirnames = list(reversed(dirnames))
74 reversed_dirnames = list(reversed(dirnames))
75 curtree = objects.Tree()
75 curtree = objects.Tree()
76 curtree[node_path] = node.mode, blob.id
76 curtree[node_path] = node.mode, blob.id
77 new_trees.append(curtree)
77 new_trees.append(curtree)
78 for dirname in reversed_dirnames[:-1]:
78 for dirname in reversed_dirnames[:-1]:
79 newtree = objects.Tree()
79 newtree = objects.Tree()
80 #newtree.add(DIRMOD, dirname, curtree.id)
80 #newtree.add(DIRMOD, dirname, curtree.id)
81 newtree[dirname] = DIRMOD, curtree.id
81 newtree[dirname] = DIRMOD, curtree.id
82 new_trees.append(newtree)
82 new_trees.append(newtree)
83 curtree = newtree
83 curtree = newtree
84 parent[reversed_dirnames[-1]] = DIRMOD, curtree.id
84 parent[reversed_dirnames[-1]] = DIRMOD, curtree.id
85 else:
85 else:
86 parent.add(name=node_path, mode=node.mode, hexsha=blob.id)
86 parent.add(name=node_path, mode=node.mode, hexsha=blob.id)
87
87
88 new_trees.append(parent)
88 new_trees.append(parent)
89 # Update ancestors
89 # Update ancestors
90 for parent, tree, path in reversed([(a[1], b[1], b[0]) for a, b in
90 for parent, tree, path in reversed([(a[1], b[1], b[0]) for a, b in
91 zip(ancestors, ancestors[1:])]):
91 zip(ancestors, ancestors[1:])]):
92 parent[path] = DIRMOD, tree.id
92 parent[path] = DIRMOD, tree.id
93 object_store.add_object(tree)
93 object_store.add_object(tree)
94
94
95 object_store.add_object(blob)
95 object_store.add_object(blob)
96 for tree in new_trees:
96 for tree in new_trees:
97 object_store.add_object(tree)
97 object_store.add_object(tree)
98 for node in self.removed:
98 for node in self.removed:
99 paths = node.path.split('/')
99 paths = node.path.split('/')
100 tree = commit_tree
100 tree = commit_tree
101 trees = [tree]
101 trees = [tree]
102 # Traverse deep into the forest...
102 # Traverse deep into the forest...
103 for path in paths:
103 for path in paths:
104 try:
104 try:
105 obj = self.repository._repo[tree[path][1]]
105 obj = self.repository._repo[tree[path][1]]
106 if isinstance(obj, objects.Tree):
106 if isinstance(obj, objects.Tree):
107 trees.append(obj)
107 trees.append(obj)
108 tree = obj
108 tree = obj
109 except KeyError:
109 except KeyError:
110 break
110 break
111 # Cut down the blob and all rotten trees on the way back...
111 # Cut down the blob and all rotten trees on the way back...
112 for path, tree in reversed(zip(paths, trees)):
112 for path, tree in reversed(zip(paths, trees)):
113 del tree[path]
113 del tree[path]
114 if tree:
114 if tree:
115 # This tree still has elements - don't remove it or any
115 # This tree still has elements - don't remove it or any
116 # of it's parents
116 # of it's parents
117 break
117 break
118
118
119 object_store.add_object(commit_tree)
119 object_store.add_object(commit_tree)
120
120
121 # Create commit
121 # Create commit
122 commit = objects.Commit()
122 commit = objects.Commit()
123 commit.tree = commit_tree.id
123 commit.tree = commit_tree.id
124 commit.parents = [p._commit.id for p in self.parents if p]
124 commit.parents = [p._commit.id for p in self.parents if p]
125 commit.author = commit.committer = safe_str(author)
125 commit.author = commit.committer = safe_str(author)
126 commit.encoding = ENCODING
126 commit.encoding = ENCODING
127 commit.message = safe_str(message)
127 commit.message = safe_str(message)
128
128
129 # Compute date
129 # Compute date
130 if date is None:
130 if date is None:
131 date = time.time()
131 date = time.time()
132 elif isinstance(date, datetime.datetime):
132 elif isinstance(date, datetime.datetime):
133 date = time.mktime(date.timetuple())
133 date = time.mktime(date.timetuple())
134
134
135 author_time = kwargs.pop('author_time', date)
135 author_time = kwargs.pop('author_time', date)
136 commit.commit_time = int(date)
136 commit.commit_time = int(date)
137 commit.author_time = int(author_time)
137 commit.author_time = int(author_time)
138 tz = time.timezone
138 tz = time.timezone
139 author_tz = kwargs.pop('author_timezone', tz)
139 author_tz = kwargs.pop('author_timezone', tz)
140 commit.commit_timezone = tz
140 commit.commit_timezone = tz
141 commit.author_timezone = author_tz
141 commit.author_timezone = author_tz
142
142
143 object_store.add_object(commit)
143 object_store.add_object(commit)
144
144
145 ref = 'refs/heads/%s' % branch
145 ref = 'refs/heads/%s' % branch
146 repo.refs[ref] = commit.id
146 repo.refs[ref] = commit.id
147 repo.refs.set_symbolic_ref('HEAD', ref)
147 repo.refs.set_symbolic_ref('HEAD', ref)
148
148
149 # Update vcs repository object & recreate dulwich repo
149 # Update vcs repository object & recreate dulwich repo
150 self.repository.revisions.append(commit.id)
150 self.repository.revisions.append(commit.id)
151 self.repository._repo = Repo(self.repository.path)
151 self.repository._repo = Repo(self.repository.path)
152 # invalidate parsed refs after commit
153 self.repository._parsed_refs = self.repository._get_parsed_refs()
152 tip = self.repository.get_changeset()
154 tip = self.repository.get_changeset()
153 self.reset()
155 self.reset()
154 return tip
156 return tip
155
157
156 def _get_missing_trees(self, path, root_tree):
158 def _get_missing_trees(self, path, root_tree):
157 """
159 """
158 Creates missing ``Tree`` objects for the given path.
160 Creates missing ``Tree`` objects for the given path.
159
161
160 :param path: path given as a string. It may be a path to a file node
162 :param path: path given as a string. It may be a path to a file node
161 (i.e. ``foo/bar/baz.txt``) or directory path - in that case it must
163 (i.e. ``foo/bar/baz.txt``) or directory path - in that case it must
162 end with slash (i.e. ``foo/bar/``).
164 end with slash (i.e. ``foo/bar/``).
163 :param root_tree: ``dulwich.objects.Tree`` object from which we start
165 :param root_tree: ``dulwich.objects.Tree`` object from which we start
164 traversing (should be commit's root tree)
166 traversing (should be commit's root tree)
165 """
167 """
166 dirpath = posixpath.split(path)[0]
168 dirpath = posixpath.split(path)[0]
167 dirs = dirpath.split('/')
169 dirs = dirpath.split('/')
168 if not dirs or dirs == ['']:
170 if not dirs or dirs == ['']:
169 return []
171 return []
170
172
171 def get_tree_for_dir(tree, dirname):
173 def get_tree_for_dir(tree, dirname):
172 for name, mode, id in tree.iteritems():
174 for name, mode, id in tree.iteritems():
173 if name == dirname:
175 if name == dirname:
174 obj = self.repository._repo[id]
176 obj = self.repository._repo[id]
175 if isinstance(obj, objects.Tree):
177 if isinstance(obj, objects.Tree):
176 return obj
178 return obj
177 else:
179 else:
178 raise RepositoryError("Cannot create directory %s "
180 raise RepositoryError("Cannot create directory %s "
179 "at tree %s as path is occupied and is not a "
181 "at tree %s as path is occupied and is not a "
180 "Tree" % (dirname, tree))
182 "Tree" % (dirname, tree))
181 return None
183 return None
182
184
183 trees = []
185 trees = []
184 parent = root_tree
186 parent = root_tree
185 for dirname in dirs:
187 for dirname in dirs:
186 tree = get_tree_for_dir(parent, dirname)
188 tree = get_tree_for_dir(parent, dirname)
187 if tree is None:
189 if tree is None:
188 tree = objects.Tree()
190 tree = objects.Tree()
189 dirmode = 040000
191 dirmode = 040000
190 parent.add(dirmode, dirname, tree.id)
192 parent.add(dirmode, dirname, tree.id)
191 parent = tree
193 parent = tree
192 # Always append tree
194 # Always append tree
193 trees.append(tree)
195 trees.append(tree)
194 return trees
196 return trees
@@ -1,599 +1,604 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 vcs.backends.git
3 vcs.backends.git
4 ~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~
5
5
6 Git backend implementation.
6 Git backend implementation.
7
7
8 :created_on: Apr 8, 2010
8 :created_on: Apr 8, 2010
9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
10 """
10 """
11
11
12 import os
12 import os
13 import re
13 import re
14 import time
14 import time
15 import posixpath
15 import posixpath
16 from dulwich.repo import Repo, NotGitRepository
16 from dulwich.repo import Repo, NotGitRepository
17 #from dulwich.config import ConfigFile
17 #from dulwich.config import ConfigFile
18 from string import Template
18 from string import Template
19 from subprocess import Popen, PIPE
19 from subprocess import Popen, PIPE
20 from rhodecode.lib.vcs.backends.base import BaseRepository
20 from rhodecode.lib.vcs.backends.base import BaseRepository
21 from rhodecode.lib.vcs.exceptions import BranchDoesNotExistError
21 from rhodecode.lib.vcs.exceptions import BranchDoesNotExistError
22 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
22 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
23 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
23 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
24 from rhodecode.lib.vcs.exceptions import RepositoryError
24 from rhodecode.lib.vcs.exceptions import RepositoryError
25 from rhodecode.lib.vcs.exceptions import TagAlreadyExistError
25 from rhodecode.lib.vcs.exceptions import TagAlreadyExistError
26 from rhodecode.lib.vcs.exceptions import TagDoesNotExistError
26 from rhodecode.lib.vcs.exceptions import TagDoesNotExistError
27 from rhodecode.lib.vcs.utils import safe_unicode, makedate, date_fromtimestamp
27 from rhodecode.lib.vcs.utils import safe_unicode, makedate, date_fromtimestamp
28 from rhodecode.lib.vcs.utils.lazy import LazyProperty
28 from rhodecode.lib.vcs.utils.lazy import LazyProperty
29 from rhodecode.lib.vcs.utils.ordered_dict import OrderedDict
29 from rhodecode.lib.vcs.utils.ordered_dict import OrderedDict
30 from rhodecode.lib.vcs.utils.paths import abspath
30 from rhodecode.lib.vcs.utils.paths import abspath
31 from rhodecode.lib.vcs.utils.paths import get_user_home
31 from rhodecode.lib.vcs.utils.paths import get_user_home
32 from .workdir import GitWorkdir
32 from .workdir import GitWorkdir
33 from .changeset import GitChangeset
33 from .changeset import GitChangeset
34 from .inmemory import GitInMemoryChangeset
34 from .inmemory import GitInMemoryChangeset
35 from .config import ConfigFile
35 from .config import ConfigFile
36
36
37
37
38 class GitRepository(BaseRepository):
38 class GitRepository(BaseRepository):
39 """
39 """
40 Git repository backend.
40 Git repository backend.
41 """
41 """
42 DEFAULT_BRANCH_NAME = 'master'
42 DEFAULT_BRANCH_NAME = 'master'
43 scm = 'git'
43 scm = 'git'
44
44
45 def __init__(self, repo_path, create=False, src_url=None,
45 def __init__(self, repo_path, create=False, src_url=None,
46 update_after_clone=False, bare=False):
46 update_after_clone=False, bare=False):
47
47
48 self.path = abspath(repo_path)
48 self.path = abspath(repo_path)
49 self._repo = self._get_repo(create, src_url, update_after_clone, bare)
49 self._repo = self._get_repo(create, src_url, update_after_clone, bare)
50 #temporary set that to now at later we will move it to constructor
50 #temporary set that to now at later we will move it to constructor
51 baseui = None
51 baseui = None
52 if baseui is None:
52 if baseui is None:
53 from mercurial.ui import ui
53 from mercurial.ui import ui
54 baseui = ui()
54 baseui = ui()
55 # patch the instance of GitRepo with an "FAKE" ui object to add
55 # patch the instance of GitRepo with an "FAKE" ui object to add
56 # compatibility layer with Mercurial
56 # compatibility layer with Mercurial
57 setattr(self._repo, 'ui', baseui)
57 setattr(self._repo, 'ui', baseui)
58
58
59 try:
59 try:
60 self.head = self._repo.head()
60 self.head = self._repo.head()
61 except KeyError:
61 except KeyError:
62 self.head = None
62 self.head = None
63
63
64 self._config_files = [
64 self._config_files = [
65 bare and abspath(self.path, 'config') or abspath(self.path, '.git',
65 bare and abspath(self.path, 'config') or abspath(self.path, '.git',
66 'config'),
66 'config'),
67 abspath(get_user_home(), '.gitconfig'),
67 abspath(get_user_home(), '.gitconfig'),
68 ]
68 ]
69 self.bare = self._repo.bare
69 self.bare = self._repo.bare
70
70
71 @LazyProperty
71 @LazyProperty
72 def revisions(self):
72 def revisions(self):
73 """
73 """
74 Returns list of revisions' ids, in ascending order. Being lazy
74 Returns list of revisions' ids, in ascending order. Being lazy
75 attribute allows external tools to inject shas from cache.
75 attribute allows external tools to inject shas from cache.
76 """
76 """
77 return self._get_all_revisions()
77 return self._get_all_revisions()
78
78
79 def run_git_command(self, cmd):
79 def run_git_command(self, cmd):
80 """
80 """
81 Runs given ``cmd`` as git command and returns tuple
81 Runs given ``cmd`` as git command and returns tuple
82 (returncode, stdout, stderr).
82 (returncode, stdout, stderr).
83
83
84 .. note::
84 .. note::
85 This method exists only until log/blame functionality is implemented
85 This method exists only until log/blame functionality is implemented
86 at Dulwich (see https://bugs.launchpad.net/bugs/645142). Parsing
86 at Dulwich (see https://bugs.launchpad.net/bugs/645142). Parsing
87 os command's output is road to hell...
87 os command's output is road to hell...
88
88
89 :param cmd: git command to be executed
89 :param cmd: git command to be executed
90 """
90 """
91
91
92 _copts = ['-c', 'core.quotepath=false', ]
92 _copts = ['-c', 'core.quotepath=false', ]
93 _str_cmd = False
93 _str_cmd = False
94 if isinstance(cmd, basestring):
94 if isinstance(cmd, basestring):
95 cmd = [cmd]
95 cmd = [cmd]
96 _str_cmd = True
96 _str_cmd = True
97
97
98 gitenv = os.environ
98 gitenv = os.environ
99 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
99 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
100
100
101 cmd = ['git'] + _copts + cmd
101 cmd = ['git'] + _copts + cmd
102 if _str_cmd:
102 if _str_cmd:
103 cmd = ' '.join(cmd)
103 cmd = ' '.join(cmd)
104 try:
104 try:
105 opts = dict(
105 opts = dict(
106 shell=isinstance(cmd, basestring),
106 shell=isinstance(cmd, basestring),
107 stdout=PIPE,
107 stdout=PIPE,
108 stderr=PIPE,
108 stderr=PIPE,
109 env=gitenv,
109 env=gitenv,
110 )
110 )
111 if os.path.isdir(self.path):
111 if os.path.isdir(self.path):
112 opts['cwd'] = self.path
112 opts['cwd'] = self.path
113 p = Popen(cmd, **opts)
113 p = Popen(cmd, **opts)
114 except OSError, err:
114 except OSError, err:
115 raise RepositoryError("Couldn't run git command (%s).\n"
115 raise RepositoryError("Couldn't run git command (%s).\n"
116 "Original error was:%s" % (cmd, err))
116 "Original error was:%s" % (cmd, err))
117 so, se = p.communicate()
117 so, se = p.communicate()
118 if not se.startswith("fatal: bad default revision 'HEAD'") and \
118 if not se.startswith("fatal: bad default revision 'HEAD'") and \
119 p.returncode != 0:
119 p.returncode != 0:
120 raise RepositoryError("Couldn't run git command (%s).\n"
120 raise RepositoryError("Couldn't run git command (%s).\n"
121 "stderr:\n%s" % (cmd, se))
121 "stderr:\n%s" % (cmd, se))
122 return so, se
122 return so, se
123
123
124 def _check_url(self, url):
124 def _check_url(self, url):
125 """
125 """
126 Functon will check given url and try to verify if it's a valid
126 Functon will check given url and try to verify if it's a valid
127 link. Sometimes it may happened that mercurial will issue basic
127 link. Sometimes it may happened that mercurial will issue basic
128 auth request that can cause whole API to hang when used from python
128 auth request that can cause whole API to hang when used from python
129 or other external calls.
129 or other external calls.
130
130
131 On failures it'll raise urllib2.HTTPError
131 On failures it'll raise urllib2.HTTPError
132 """
132 """
133
133
134 #TODO: implement this
134 #TODO: implement this
135 pass
135 pass
136
136
137 def _get_repo(self, create, src_url=None, update_after_clone=False,
137 def _get_repo(self, create, src_url=None, update_after_clone=False,
138 bare=False):
138 bare=False):
139 if create and os.path.exists(self.path):
139 if create and os.path.exists(self.path):
140 raise RepositoryError("Location already exist")
140 raise RepositoryError("Location already exist")
141 if src_url and not create:
141 if src_url and not create:
142 raise RepositoryError("Create should be set to True if src_url is "
142 raise RepositoryError("Create should be set to True if src_url is "
143 "given (clone operation creates repository)")
143 "given (clone operation creates repository)")
144 try:
144 try:
145 if create and src_url:
145 if create and src_url:
146 self._check_url(src_url)
146 self._check_url(src_url)
147 self.clone(src_url, update_after_clone, bare)
147 self.clone(src_url, update_after_clone, bare)
148 return Repo(self.path)
148 return Repo(self.path)
149 elif create:
149 elif create:
150 os.mkdir(self.path)
150 os.mkdir(self.path)
151 if bare:
151 if bare:
152 return Repo.init_bare(self.path)
152 return Repo.init_bare(self.path)
153 else:
153 else:
154 return Repo.init(self.path)
154 return Repo.init(self.path)
155 else:
155 else:
156 return Repo(self.path)
156 return Repo(self.path)
157 except (NotGitRepository, OSError), err:
157 except (NotGitRepository, OSError), err:
158 raise RepositoryError(err)
158 raise RepositoryError(err)
159
159
160 def _get_all_revisions(self):
160 def _get_all_revisions(self):
161 cmd = 'rev-list --all --reverse --date-order'
161 cmd = 'rev-list --all --reverse --date-order'
162 try:
162 try:
163 so, se = self.run_git_command(cmd)
163 so, se = self.run_git_command(cmd)
164 except RepositoryError:
164 except RepositoryError:
165 # Can be raised for empty repositories
165 # Can be raised for empty repositories
166 return []
166 return []
167 return so.splitlines()
167 return so.splitlines()
168
168
169 def _get_all_revisions2(self):
169 def _get_all_revisions2(self):
170 #alternate implementation using dulwich
170 #alternate implementation using dulwich
171 includes = [x[1][0] for x in self._parsed_refs.iteritems()
171 includes = [x[1][0] for x in self._parsed_refs.iteritems()
172 if x[1][1] != 'T']
172 if x[1][1] != 'T']
173 return [c.commit.id for c in self._repo.get_walker(include=includes)]
173 return [c.commit.id for c in self._repo.get_walker(include=includes)]
174
174
175 def _get_revision(self, revision):
175 def _get_revision(self, revision):
176 """
176 """
177 For git backend we always return integer here. This way we ensure
177 For git backend we always return integer here. This way we ensure
178 that changset's revision attribute would become integer.
178 that changset's revision attribute would become integer.
179 """
179 """
180 pattern = re.compile(r'^[[0-9a-fA-F]{12}|[0-9a-fA-F]{40}]$')
180 pattern = re.compile(r'^[[0-9a-fA-F]{12}|[0-9a-fA-F]{40}]$')
181 is_bstr = lambda o: isinstance(o, (str, unicode))
181 is_bstr = lambda o: isinstance(o, (str, unicode))
182 is_null = lambda o: len(o) == revision.count('0')
182 is_null = lambda o: len(o) == revision.count('0')
183
183
184 if len(self.revisions) == 0:
184 if len(self.revisions) == 0:
185 raise EmptyRepositoryError("There are no changesets yet")
185 raise EmptyRepositoryError("There are no changesets yet")
186
186
187 if revision in (None, '', 'tip', 'HEAD', 'head', -1):
187 if revision in (None, '', 'tip', 'HEAD', 'head', -1):
188 revision = self.revisions[-1]
188 revision = self.revisions[-1]
189
189
190 if ((is_bstr(revision) and revision.isdigit() and len(revision) < 12)
190 if ((is_bstr(revision) and revision.isdigit() and len(revision) < 12)
191 or isinstance(revision, int) or is_null(revision)):
191 or isinstance(revision, int) or is_null(revision)):
192 try:
192 try:
193 revision = self.revisions[int(revision)]
193 revision = self.revisions[int(revision)]
194 except:
194 except:
195 raise ChangesetDoesNotExistError("Revision %r does not exist "
195 raise ChangesetDoesNotExistError("Revision %r does not exist "
196 "for this repository %s" % (revision, self))
196 "for this repository %s" % (revision, self))
197
197
198 elif is_bstr(revision):
198 elif is_bstr(revision):
199 # get by branch/tag name
199 # get by branch/tag name
200 _ref_revision = self._parsed_refs.get(revision)
200 _ref_revision = self._parsed_refs.get(revision)
201 _tags_shas = self.tags.values()
201 _tags_shas = self.tags.values()
202 if _ref_revision: # and _ref_revision[1] in ['H', 'RH', 'T']:
202 if _ref_revision: # and _ref_revision[1] in ['H', 'RH', 'T']:
203 return _ref_revision[0]
203 return _ref_revision[0]
204
204
205 # maybe it's a tag ? we don't have them in self.revisions
205 # maybe it's a tag ? we don't have them in self.revisions
206 elif revision in _tags_shas:
206 elif revision in _tags_shas:
207 return _tags_shas[_tags_shas.index(revision)]
207 return _tags_shas[_tags_shas.index(revision)]
208
208
209 elif not pattern.match(revision) or revision not in self.revisions:
209 elif not pattern.match(revision) or revision not in self.revisions:
210 raise ChangesetDoesNotExistError("Revision %r does not exist "
210 raise ChangesetDoesNotExistError("Revision %r does not exist "
211 "for this repository %s" % (revision, self))
211 "for this repository %s" % (revision, self))
212
212
213 # Ensure we return full id
213 # Ensure we return full id
214 if not pattern.match(str(revision)):
214 if not pattern.match(str(revision)):
215 raise ChangesetDoesNotExistError("Given revision %r not recognized"
215 raise ChangesetDoesNotExistError("Given revision %r not recognized"
216 % revision)
216 % revision)
217 return revision
217 return revision
218
218
219 def _get_archives(self, archive_name='tip'):
219 def _get_archives(self, archive_name='tip'):
220
220
221 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
221 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
222 yield {"type": i[0], "extension": i[1], "node": archive_name}
222 yield {"type": i[0], "extension": i[1], "node": archive_name}
223
223
224 def _get_url(self, url):
224 def _get_url(self, url):
225 """
225 """
226 Returns normalized url. If schema is not given, would fall to
226 Returns normalized url. If schema is not given, would fall to
227 filesystem (``file:///``) schema.
227 filesystem (``file:///``) schema.
228 """
228 """
229 url = str(url)
229 url = str(url)
230 if url != 'default' and not '://' in url:
230 if url != 'default' and not '://' in url:
231 url = ':///'.join(('file', url))
231 url = ':///'.join(('file', url))
232 return url
232 return url
233
233
234 @LazyProperty
234 @LazyProperty
235 def name(self):
235 def name(self):
236 return os.path.basename(self.path)
236 return os.path.basename(self.path)
237
237
238 @LazyProperty
238 @LazyProperty
239 def last_change(self):
239 def last_change(self):
240 """
240 """
241 Returns last change made on this repository as datetime object
241 Returns last change made on this repository as datetime object
242 """
242 """
243 return date_fromtimestamp(self._get_mtime(), makedate()[1])
243 return date_fromtimestamp(self._get_mtime(), makedate()[1])
244
244
245 def _get_mtime(self):
245 def _get_mtime(self):
246 try:
246 try:
247 return time.mktime(self.get_changeset().date.timetuple())
247 return time.mktime(self.get_changeset().date.timetuple())
248 except RepositoryError:
248 except RepositoryError:
249 idx_loc = '' if self.bare else '.git'
249 idx_loc = '' if self.bare else '.git'
250 # fallback to filesystem
250 # fallback to filesystem
251 in_path = os.path.join(self.path, idx_loc, "index")
251 in_path = os.path.join(self.path, idx_loc, "index")
252 he_path = os.path.join(self.path, idx_loc, "HEAD")
252 he_path = os.path.join(self.path, idx_loc, "HEAD")
253 if os.path.exists(in_path):
253 if os.path.exists(in_path):
254 return os.stat(in_path).st_mtime
254 return os.stat(in_path).st_mtime
255 else:
255 else:
256 return os.stat(he_path).st_mtime
256 return os.stat(he_path).st_mtime
257
257
258 @LazyProperty
258 @LazyProperty
259 def description(self):
259 def description(self):
260 idx_loc = '' if self.bare else '.git'
260 idx_loc = '' if self.bare else '.git'
261 undefined_description = u'unknown'
261 undefined_description = u'unknown'
262 description_path = os.path.join(self.path, idx_loc, 'description')
262 description_path = os.path.join(self.path, idx_loc, 'description')
263 if os.path.isfile(description_path):
263 if os.path.isfile(description_path):
264 return safe_unicode(open(description_path).read())
264 return safe_unicode(open(description_path).read())
265 else:
265 else:
266 return undefined_description
266 return undefined_description
267
267
268 @LazyProperty
268 @LazyProperty
269 def contact(self):
269 def contact(self):
270 undefined_contact = u'Unknown'
270 undefined_contact = u'Unknown'
271 return undefined_contact
271 return undefined_contact
272
272
273 @property
273 @property
274 def branches(self):
274 def branches(self):
275 if not self.revisions:
275 if not self.revisions:
276 return {}
276 return {}
277 sortkey = lambda ctx: ctx[0]
277 sortkey = lambda ctx: ctx[0]
278 _branches = [(x[0], x[1][0])
278 _branches = [(x[0], x[1][0])
279 for x in self._parsed_refs.iteritems() if x[1][1] == 'H']
279 for x in self._parsed_refs.iteritems() if x[1][1] == 'H']
280 return OrderedDict(sorted(_branches, key=sortkey, reverse=False))
280 return OrderedDict(sorted(_branches, key=sortkey, reverse=False))
281
281
282 @LazyProperty
282 @LazyProperty
283 def tags(self):
283 def tags(self):
284 return self._get_tags()
284 return self._get_tags()
285
285
286 def _get_tags(self):
286 def _get_tags(self):
287 if not self.revisions:
287 if not self.revisions:
288 return {}
288 return {}
289
289
290 sortkey = lambda ctx: ctx[0]
290 sortkey = lambda ctx: ctx[0]
291 _tags = [(x[0], x[1][0])
291 _tags = [(x[0], x[1][0])
292 for x in self._parsed_refs.iteritems() if x[1][1] == 'T']
292 for x in self._parsed_refs.iteritems() if x[1][1] == 'T']
293 return OrderedDict(sorted(_tags, key=sortkey, reverse=True))
293 return OrderedDict(sorted(_tags, key=sortkey, reverse=True))
294
294
295 def tag(self, name, user, revision=None, message=None, date=None,
295 def tag(self, name, user, revision=None, message=None, date=None,
296 **kwargs):
296 **kwargs):
297 """
297 """
298 Creates and returns a tag for the given ``revision``.
298 Creates and returns a tag for the given ``revision``.
299
299
300 :param name: name for new tag
300 :param name: name for new tag
301 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
301 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
302 :param revision: changeset id for which new tag would be created
302 :param revision: changeset id for which new tag would be created
303 :param message: message of the tag's commit
303 :param message: message of the tag's commit
304 :param date: date of tag's commit
304 :param date: date of tag's commit
305
305
306 :raises TagAlreadyExistError: if tag with same name already exists
306 :raises TagAlreadyExistError: if tag with same name already exists
307 """
307 """
308 if name in self.tags:
308 if name in self.tags:
309 raise TagAlreadyExistError("Tag %s already exists" % name)
309 raise TagAlreadyExistError("Tag %s already exists" % name)
310 changeset = self.get_changeset(revision)
310 changeset = self.get_changeset(revision)
311 message = message or "Added tag %s for commit %s" % (name,
311 message = message or "Added tag %s for commit %s" % (name,
312 changeset.raw_id)
312 changeset.raw_id)
313 self._repo.refs["refs/tags/%s" % name] = changeset._commit.id
313 self._repo.refs["refs/tags/%s" % name] = changeset._commit.id
314
314
315 self._parsed_refs = self._get_parsed_refs()
315 self.tags = self._get_tags()
316 self.tags = self._get_tags()
316 return changeset
317 return changeset
317
318
318 def remove_tag(self, name, user, message=None, date=None):
319 def remove_tag(self, name, user, message=None, date=None):
319 """
320 """
320 Removes tag with the given ``name``.
321 Removes tag with the given ``name``.
321
322
322 :param name: name of the tag to be removed
323 :param name: name of the tag to be removed
323 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
324 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
324 :param message: message of the tag's removal commit
325 :param message: message of the tag's removal commit
325 :param date: date of tag's removal commit
326 :param date: date of tag's removal commit
326
327
327 :raises TagDoesNotExistError: if tag with given name does not exists
328 :raises TagDoesNotExistError: if tag with given name does not exists
328 """
329 """
329 if name not in self.tags:
330 if name not in self.tags:
330 raise TagDoesNotExistError("Tag %s does not exist" % name)
331 raise TagDoesNotExistError("Tag %s does not exist" % name)
331 tagpath = posixpath.join(self._repo.refs.path, 'refs', 'tags', name)
332 tagpath = posixpath.join(self._repo.refs.path, 'refs', 'tags', name)
332 try:
333 try:
333 os.remove(tagpath)
334 os.remove(tagpath)
335 self._parsed_refs = self._get_parsed_refs()
334 self.tags = self._get_tags()
336 self.tags = self._get_tags()
335 except OSError, e:
337 except OSError, e:
336 raise RepositoryError(e.strerror)
338 raise RepositoryError(e.strerror)
337
339
338 @LazyProperty
340 @LazyProperty
339 def _parsed_refs(self):
341 def _parsed_refs(self):
342 return self._get_parsed_refs()
343
344 def _get_parsed_refs(self):
340 refs = self._repo.get_refs()
345 refs = self._repo.get_refs()
341 keys = [('refs/heads/', 'H'),
346 keys = [('refs/heads/', 'H'),
342 ('refs/remotes/origin/', 'RH'),
347 ('refs/remotes/origin/', 'RH'),
343 ('refs/tags/', 'T')]
348 ('refs/tags/', 'T')]
344 _refs = {}
349 _refs = {}
345 for ref, sha in refs.iteritems():
350 for ref, sha in refs.iteritems():
346 for k, type_ in keys:
351 for k, type_ in keys:
347 if ref.startswith(k):
352 if ref.startswith(k):
348 _key = ref[len(k):]
353 _key = ref[len(k):]
349 _refs[_key] = [sha, type_]
354 _refs[_key] = [sha, type_]
350 break
355 break
351 return _refs
356 return _refs
352
357
353 def _heads(self, reverse=False):
358 def _heads(self, reverse=False):
354 refs = self._repo.get_refs()
359 refs = self._repo.get_refs()
355 heads = {}
360 heads = {}
356
361
357 for key, val in refs.items():
362 for key, val in refs.items():
358 for ref_key in ['refs/heads/', 'refs/remotes/origin/']:
363 for ref_key in ['refs/heads/', 'refs/remotes/origin/']:
359 if key.startswith(ref_key):
364 if key.startswith(ref_key):
360 n = key[len(ref_key):]
365 n = key[len(ref_key):]
361 if n not in ['HEAD']:
366 if n not in ['HEAD']:
362 heads[n] = val
367 heads[n] = val
363
368
364 return heads if reverse else dict((y, x) for x, y in heads.iteritems())
369 return heads if reverse else dict((y, x) for x, y in heads.iteritems())
365
370
366 def get_changeset(self, revision=None):
371 def get_changeset(self, revision=None):
367 """
372 """
368 Returns ``GitChangeset`` object representing commit from git repository
373 Returns ``GitChangeset`` object representing commit from git repository
369 at the given revision or head (most recent commit) if None given.
374 at the given revision or head (most recent commit) if None given.
370 """
375 """
371 if isinstance(revision, GitChangeset):
376 if isinstance(revision, GitChangeset):
372 return revision
377 return revision
373 revision = self._get_revision(revision)
378 revision = self._get_revision(revision)
374 changeset = GitChangeset(repository=self, revision=revision)
379 changeset = GitChangeset(repository=self, revision=revision)
375 return changeset
380 return changeset
376
381
377 def get_changesets(self, start=None, end=None, start_date=None,
382 def get_changesets(self, start=None, end=None, start_date=None,
378 end_date=None, branch_name=None, reverse=False):
383 end_date=None, branch_name=None, reverse=False):
379 """
384 """
380 Returns iterator of ``GitChangeset`` objects from start to end (both
385 Returns iterator of ``GitChangeset`` objects from start to end (both
381 are inclusive), in ascending date order (unless ``reverse`` is set).
386 are inclusive), in ascending date order (unless ``reverse`` is set).
382
387
383 :param start: changeset ID, as str; first returned changeset
388 :param start: changeset ID, as str; first returned changeset
384 :param end: changeset ID, as str; last returned changeset
389 :param end: changeset ID, as str; last returned changeset
385 :param start_date: if specified, changesets with commit date less than
390 :param start_date: if specified, changesets with commit date less than
386 ``start_date`` would be filtered out from returned set
391 ``start_date`` would be filtered out from returned set
387 :param end_date: if specified, changesets with commit date greater than
392 :param end_date: if specified, changesets with commit date greater than
388 ``end_date`` would be filtered out from returned set
393 ``end_date`` would be filtered out from returned set
389 :param branch_name: if specified, changesets not reachable from given
394 :param branch_name: if specified, changesets not reachable from given
390 branch would be filtered out from returned set
395 branch would be filtered out from returned set
391 :param reverse: if ``True``, returned generator would be reversed
396 :param reverse: if ``True``, returned generator would be reversed
392 (meaning that returned changesets would have descending date order)
397 (meaning that returned changesets would have descending date order)
393
398
394 :raise BranchDoesNotExistError: If given ``branch_name`` does not
399 :raise BranchDoesNotExistError: If given ``branch_name`` does not
395 exist.
400 exist.
396 :raise ChangesetDoesNotExistError: If changeset for given ``start`` or
401 :raise ChangesetDoesNotExistError: If changeset for given ``start`` or
397 ``end`` could not be found.
402 ``end`` could not be found.
398
403
399 """
404 """
400 if branch_name and branch_name not in self.branches:
405 if branch_name and branch_name not in self.branches:
401 raise BranchDoesNotExistError("Branch '%s' not found" \
406 raise BranchDoesNotExistError("Branch '%s' not found" \
402 % branch_name)
407 % branch_name)
403 # %H at format means (full) commit hash, initial hashes are retrieved
408 # %H at format means (full) commit hash, initial hashes are retrieved
404 # in ascending date order
409 # in ascending date order
405 cmd_template = 'log --date-order --reverse --pretty=format:"%H"'
410 cmd_template = 'log --date-order --reverse --pretty=format:"%H"'
406 cmd_params = {}
411 cmd_params = {}
407 if start_date:
412 if start_date:
408 cmd_template += ' --since "$since"'
413 cmd_template += ' --since "$since"'
409 cmd_params['since'] = start_date.strftime('%m/%d/%y %H:%M:%S')
414 cmd_params['since'] = start_date.strftime('%m/%d/%y %H:%M:%S')
410 if end_date:
415 if end_date:
411 cmd_template += ' --until "$until"'
416 cmd_template += ' --until "$until"'
412 cmd_params['until'] = end_date.strftime('%m/%d/%y %H:%M:%S')
417 cmd_params['until'] = end_date.strftime('%m/%d/%y %H:%M:%S')
413 if branch_name:
418 if branch_name:
414 cmd_template += ' $branch_name'
419 cmd_template += ' $branch_name'
415 cmd_params['branch_name'] = branch_name
420 cmd_params['branch_name'] = branch_name
416 else:
421 else:
417 cmd_template += ' --all'
422 cmd_template += ' --all'
418
423
419 cmd = Template(cmd_template).safe_substitute(**cmd_params)
424 cmd = Template(cmd_template).safe_substitute(**cmd_params)
420 revs = self.run_git_command(cmd)[0].splitlines()
425 revs = self.run_git_command(cmd)[0].splitlines()
421 start_pos = 0
426 start_pos = 0
422 end_pos = len(revs)
427 end_pos = len(revs)
423 if start:
428 if start:
424 _start = self._get_revision(start)
429 _start = self._get_revision(start)
425 try:
430 try:
426 start_pos = revs.index(_start)
431 start_pos = revs.index(_start)
427 except ValueError:
432 except ValueError:
428 pass
433 pass
429
434
430 if end is not None:
435 if end is not None:
431 _end = self._get_revision(end)
436 _end = self._get_revision(end)
432 try:
437 try:
433 end_pos = revs.index(_end)
438 end_pos = revs.index(_end)
434 except ValueError:
439 except ValueError:
435 pass
440 pass
436
441
437 if None not in [start, end] and start_pos > end_pos:
442 if None not in [start, end] and start_pos > end_pos:
438 raise RepositoryError('start cannot be after end')
443 raise RepositoryError('start cannot be after end')
439
444
440 if end_pos is not None:
445 if end_pos is not None:
441 end_pos += 1
446 end_pos += 1
442
447
443 revs = revs[start_pos:end_pos]
448 revs = revs[start_pos:end_pos]
444 if reverse:
449 if reverse:
445 revs = reversed(revs)
450 revs = reversed(revs)
446 for rev in revs:
451 for rev in revs:
447 yield self.get_changeset(rev)
452 yield self.get_changeset(rev)
448
453
449 def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
454 def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
450 context=3):
455 context=3):
451 """
456 """
452 Returns (git like) *diff*, as plain text. Shows changes introduced by
457 Returns (git like) *diff*, as plain text. Shows changes introduced by
453 ``rev2`` since ``rev1``.
458 ``rev2`` since ``rev1``.
454
459
455 :param rev1: Entry point from which diff is shown. Can be
460 :param rev1: Entry point from which diff is shown. Can be
456 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
461 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
457 the changes since empty state of the repository until ``rev2``
462 the changes since empty state of the repository until ``rev2``
458 :param rev2: Until which revision changes should be shown.
463 :param rev2: Until which revision changes should be shown.
459 :param ignore_whitespace: If set to ``True``, would not show whitespace
464 :param ignore_whitespace: If set to ``True``, would not show whitespace
460 changes. Defaults to ``False``.
465 changes. Defaults to ``False``.
461 :param context: How many lines before/after changed lines should be
466 :param context: How many lines before/after changed lines should be
462 shown. Defaults to ``3``.
467 shown. Defaults to ``3``.
463 """
468 """
464 flags = ['-U%s' % context]
469 flags = ['-U%s' % context]
465 if ignore_whitespace:
470 if ignore_whitespace:
466 flags.append('-w')
471 flags.append('-w')
467
472
468 if hasattr(rev1, 'raw_id'):
473 if hasattr(rev1, 'raw_id'):
469 rev1 = getattr(rev1, 'raw_id')
474 rev1 = getattr(rev1, 'raw_id')
470
475
471 if hasattr(rev2, 'raw_id'):
476 if hasattr(rev2, 'raw_id'):
472 rev2 = getattr(rev2, 'raw_id')
477 rev2 = getattr(rev2, 'raw_id')
473
478
474 if rev1 == self.EMPTY_CHANGESET:
479 if rev1 == self.EMPTY_CHANGESET:
475 rev2 = self.get_changeset(rev2).raw_id
480 rev2 = self.get_changeset(rev2).raw_id
476 cmd = ' '.join(['show'] + flags + [rev2])
481 cmd = ' '.join(['show'] + flags + [rev2])
477 else:
482 else:
478 rev1 = self.get_changeset(rev1).raw_id
483 rev1 = self.get_changeset(rev1).raw_id
479 rev2 = self.get_changeset(rev2).raw_id
484 rev2 = self.get_changeset(rev2).raw_id
480 cmd = ' '.join(['diff'] + flags + [rev1, rev2])
485 cmd = ' '.join(['diff'] + flags + [rev1, rev2])
481
486
482 if path:
487 if path:
483 cmd += ' -- "%s"' % path
488 cmd += ' -- "%s"' % path
484 stdout, stderr = self.run_git_command(cmd)
489 stdout, stderr = self.run_git_command(cmd)
485 # If we used 'show' command, strip first few lines (until actual diff
490 # If we used 'show' command, strip first few lines (until actual diff
486 # starts)
491 # starts)
487 if rev1 == self.EMPTY_CHANGESET:
492 if rev1 == self.EMPTY_CHANGESET:
488 lines = stdout.splitlines()
493 lines = stdout.splitlines()
489 x = 0
494 x = 0
490 for line in lines:
495 for line in lines:
491 if line.startswith('diff'):
496 if line.startswith('diff'):
492 break
497 break
493 x += 1
498 x += 1
494 # Append new line just like 'diff' command do
499 # Append new line just like 'diff' command do
495 stdout = '\n'.join(lines[x:]) + '\n'
500 stdout = '\n'.join(lines[x:]) + '\n'
496 return stdout
501 return stdout
497
502
498 @LazyProperty
503 @LazyProperty
499 def in_memory_changeset(self):
504 def in_memory_changeset(self):
500 """
505 """
501 Returns ``GitInMemoryChangeset`` object for this repository.
506 Returns ``GitInMemoryChangeset`` object for this repository.
502 """
507 """
503 return GitInMemoryChangeset(self)
508 return GitInMemoryChangeset(self)
504
509
505 def clone(self, url, update_after_clone=True, bare=False):
510 def clone(self, url, update_after_clone=True, bare=False):
506 """
511 """
507 Tries to clone changes from external location.
512 Tries to clone changes from external location.
508
513
509 :param update_after_clone: If set to ``False``, git won't checkout
514 :param update_after_clone: If set to ``False``, git won't checkout
510 working directory
515 working directory
511 :param bare: If set to ``True``, repository would be cloned into
516 :param bare: If set to ``True``, repository would be cloned into
512 *bare* git repository (no working directory at all).
517 *bare* git repository (no working directory at all).
513 """
518 """
514 url = self._get_url(url)
519 url = self._get_url(url)
515 cmd = ['clone']
520 cmd = ['clone']
516 if bare:
521 if bare:
517 cmd.append('--bare')
522 cmd.append('--bare')
518 elif not update_after_clone:
523 elif not update_after_clone:
519 cmd.append('--no-checkout')
524 cmd.append('--no-checkout')
520 cmd += ['--', '"%s"' % url, '"%s"' % self.path]
525 cmd += ['--', '"%s"' % url, '"%s"' % self.path]
521 cmd = ' '.join(cmd)
526 cmd = ' '.join(cmd)
522 # If error occurs run_git_command raises RepositoryError already
527 # If error occurs run_git_command raises RepositoryError already
523 self.run_git_command(cmd)
528 self.run_git_command(cmd)
524
529
525 def pull(self, url):
530 def pull(self, url):
526 """
531 """
527 Tries to pull changes from external location.
532 Tries to pull changes from external location.
528 """
533 """
529 url = self._get_url(url)
534 url = self._get_url(url)
530 cmd = ['pull']
535 cmd = ['pull']
531 cmd.append("--ff-only")
536 cmd.append("--ff-only")
532 cmd.append(url)
537 cmd.append(url)
533 cmd = ' '.join(cmd)
538 cmd = ' '.join(cmd)
534 # If error occurs run_git_command raises RepositoryError already
539 # If error occurs run_git_command raises RepositoryError already
535 self.run_git_command(cmd)
540 self.run_git_command(cmd)
536
541
537 def fetch(self, url):
542 def fetch(self, url):
538 """
543 """
539 Tries to pull changes from external location.
544 Tries to pull changes from external location.
540 """
545 """
541 url = self._get_url(url)
546 url = self._get_url(url)
542 cmd = ['fetch']
547 cmd = ['fetch']
543 cmd.append(url)
548 cmd.append(url)
544 cmd = ' '.join(cmd)
549 cmd = ' '.join(cmd)
545 # If error occurs run_git_command raises RepositoryError already
550 # If error occurs run_git_command raises RepositoryError already
546 self.run_git_command(cmd)
551 self.run_git_command(cmd)
547
552
548 @LazyProperty
553 @LazyProperty
549 def workdir(self):
554 def workdir(self):
550 """
555 """
551 Returns ``Workdir`` instance for this repository.
556 Returns ``Workdir`` instance for this repository.
552 """
557 """
553 return GitWorkdir(self)
558 return GitWorkdir(self)
554
559
555 def get_config_value(self, section, name, config_file=None):
560 def get_config_value(self, section, name, config_file=None):
556 """
561 """
557 Returns configuration value for a given [``section``] and ``name``.
562 Returns configuration value for a given [``section``] and ``name``.
558
563
559 :param section: Section we want to retrieve value from
564 :param section: Section we want to retrieve value from
560 :param name: Name of configuration we want to retrieve
565 :param name: Name of configuration we want to retrieve
561 :param config_file: A path to file which should be used to retrieve
566 :param config_file: A path to file which should be used to retrieve
562 configuration from (might also be a list of file paths)
567 configuration from (might also be a list of file paths)
563 """
568 """
564 if config_file is None:
569 if config_file is None:
565 config_file = []
570 config_file = []
566 elif isinstance(config_file, basestring):
571 elif isinstance(config_file, basestring):
567 config_file = [config_file]
572 config_file = [config_file]
568
573
569 def gen_configs():
574 def gen_configs():
570 for path in config_file + self._config_files:
575 for path in config_file + self._config_files:
571 try:
576 try:
572 yield ConfigFile.from_path(path)
577 yield ConfigFile.from_path(path)
573 except (IOError, OSError, ValueError):
578 except (IOError, OSError, ValueError):
574 continue
579 continue
575
580
576 for config in gen_configs():
581 for config in gen_configs():
577 try:
582 try:
578 return config.get(section, name)
583 return config.get(section, name)
579 except KeyError:
584 except KeyError:
580 continue
585 continue
581 return None
586 return None
582
587
583 def get_user_name(self, config_file=None):
588 def get_user_name(self, config_file=None):
584 """
589 """
585 Returns user's name from global configuration file.
590 Returns user's name from global configuration file.
586
591
587 :param config_file: A path to file which should be used to retrieve
592 :param config_file: A path to file which should be used to retrieve
588 configuration from (might also be a list of file paths)
593 configuration from (might also be a list of file paths)
589 """
594 """
590 return self.get_config_value('user', 'name', config_file)
595 return self.get_config_value('user', 'name', config_file)
591
596
592 def get_user_email(self, config_file=None):
597 def get_user_email(self, config_file=None):
593 """
598 """
594 Returns user's email from global configuration file.
599 Returns user's email from global configuration file.
595
600
596 :param config_file: A path to file which should be used to retrieve
601 :param config_file: A path to file which should be used to retrieve
597 configuration from (might also be a list of file paths)
602 configuration from (might also be a list of file paths)
598 """
603 """
599 return self.get_config_value('user', 'email', config_file)
604 return self.get_config_value('user', 'email', config_file)
General Comments 0
You need to be logged in to leave comments. Login now