1 from __future__
import division
, absolute_import
, unicode_literals
7 from ..observable
import Observable
9 # put summary at the end b/c it can contain
10 # any number of funky characters, including the separator
11 logfmt
= 'format:%H%x01%P%x01%d%x01%an%x01%ad%x01%ae%x01%s'
15 class CommitFactory(object):
22 cls
.root_generation
= 0
25 def new(cls
, sha1
=None, log_entry
=None):
26 if not sha1
and log_entry
:
29 commit
= cls
.commits
[sha1
]
30 if log_entry
and not commit
.parsed
:
31 commit
.parse(log_entry
)
32 cls
.root_generation
= max(commit
.generation
,
35 commit
= Commit(sha1
=sha1
,
38 cls
.root_generation
+= 1
39 commit
.generation
= max(commit
.generation
,
41 cls
.commits
[sha1
] = commit
45 class DAG(Observable
):
46 ref_updated
= 'ref_updated'
47 count_updated
= 'count_updated'
49 def __init__(self
, ref
, count
):
50 Observable
.__init
__(self
)
55 def set_ref(self
, ref
):
56 changed
= ref
!= self
.ref
59 self
.notify_observers(self
.ref_updated
)
62 def set_count(self
, count
):
63 changed
= count
!= self
.count
66 self
.notify_observers(self
.count_updated
)
69 def set_arguments(self
, args
):
72 if self
.set_count(args
.count
):
73 self
.overrides
['count'] = args
.count
75 if hasattr(args
, 'args') and args
.args
:
76 ref
= core
.list2cmdline(args
.args
)
78 self
.overrides
['ref'] = ref
80 def overridden(self
, opt
):
81 return opt
in self
.overrides
84 all_refs
= utils
.shell_split(self
.ref
)
86 all_refs
= all_refs
[all_refs
.index('--'):]
88 return [p
for p
in all_refs
if p
and core
.exists(p
)]
105 def __init__(self
, sha1
=None, log_entry
=None):
115 self
.generation
= CommitFactory
.root_generation
117 self
.parse(log_entry
)
119 def parse(self
, log_entry
, sep
=logsep
):
120 self
.sha1
= log_entry
[:40]
121 after_sha1
= log_entry
[41:]
122 details
= after_sha1
.split(sep
, 5)
123 (parents
, tags
, author
, authdate
, email
, summary
) = details
125 self
.summary
= summary
and summary
or ''
126 self
.author
= author
and author
or ''
127 self
.authdate
= authdate
or ''
128 self
.email
= email
and email
or ''
132 for parent_sha1
in parents
.split(' '):
133 parent
= CommitFactory
.new(sha1
=parent_sha1
)
134 parent
.children
.append(self
)
135 if generation
is None:
136 generation
= parent
.generation
+1
137 self
.parents
.append(parent
)
138 generation
= max(parent
.generation
+1, generation
)
139 self
.generation
= generation
142 for tag
in tags
[2:-1].split(', '):
148 def add_label(self
, tag
):
149 """Add tag/branch labels from `git log --decorate ....`"""
151 if tag
.startswith('tag: '):
152 tag
= tag
[5:] # tag: refs/
153 elif tag
.startswith('refs/remotes/'):
154 tag
= tag
[13:] # refs/remotes/
155 elif tag
.startswith('refs/heads/'):
156 tag
= tag
[11:] # refs/heads/
157 if tag
.endswith('/HEAD'):
160 # Git 2.4 Release Notes (draft)
161 # =============================
163 # Backward compatibility warning(s)
164 # ---------------------------------
166 # This release has a few changes in the user-visible output from
167 # Porcelain commands. These are not meant to be parsed by scripts, but
168 # the users still may want to be aware of the changes:
170 # * Output from "git log --decorate" (and "%d" format specifier used in
171 # the userformat "--format=<string>" parameter "git log" family of
172 # command takes) used to list "HEAD" just like other tips of branch
173 # names, separated with a comma in between. E.g.
175 # $ git log --decorate -1 master
176 # commit bdb0f6788fa5e3cacc4315e9ff318a27b2676ff4 (HEAD, master)
179 # This release updates the output slightly when HEAD refers to the tip
180 # of a branch whose name is also shown in the output. The above is
183 # $ git log --decorate -1 master
184 # commit bdb0f6788fa5e3cacc4315e9ff318a27b2676ff4 (HEAD -> master)
187 # C.f. http://thread.gmane.org/gmane.linux.kernel/1931234
189 head_arrow
= 'HEAD -> '
190 if tag
.startswith(head_arrow
):
191 self
.tags
.add('HEAD')
192 self
.tags
.add(tag
[len(head_arrow
):])
202 'summary': self
.summary
,
203 'author': self
.author
,
204 'authdate': self
.authdate
,
205 'parents': [p
.sha1
for p
in self
.parents
],
210 return json
.dumps(self
.data(), sort_keys
=True, indent
=4)
213 ''' Returns True if the node is a fork'''
214 return len(self
.children
) > 1
217 ''' Returns True if the node is a fork'''
218 return len(self
.parents
) > 1
221 class RepoReader(object):
223 def __init__(self
, ctx
, git
=git
):
228 self
._cmd
= ['git', 'log',
233 """Indicates that all data has been read"""
235 """Index into the cached commits"""
237 """List of commits objects in topological order"""
239 cached
= property(lambda self
: self
._cached
)
240 """Return True when no commits remain to be read"""
243 return len(self
._topo
_list
)
246 CommitFactory
.reset()
263 return self
._topo
_list
[self
._idx
]
268 if self
._proc
is None:
269 ref_args
= utils
.shell_split(self
.ctx
.ref
)
270 cmd
= self
._cmd
+ ['-%d' % self
.ctx
.count
] + ref_args
271 self
._proc
= core
.start_command(cmd
)
274 log_entry
= core
.readline(self
._proc
.stdout
).rstrip()
278 self
.returncode
= self
._proc
.returncode
282 sha1
= log_entry
[:40]
284 return self
._objects
[sha1
]
286 c
= CommitFactory
.new(log_entry
=log_entry
)
287 self
._objects
[c
.sha1
] = c
288 self
._topo
_list
.append(c
)
291 __next__
= next
# for Python 3
293 def __getitem__(self
, sha1
):
294 return self
._objects
[sha1
]
297 return self
._objects
.items()