1 from __future__
import division
, absolute_import
, unicode_literals
6 from ..observable
import Observable
8 # put summary at the end b/c it can contain
9 # any number of funky characters, including the separator
10 logfmt
= r
'format:%H%x01%P%x01%d%x01%an%x01%ad%x01%ae%x01%s'
14 class CommitFactory(object):
21 cls
.root_generation
= 0
24 def new(cls
, oid
=None, log_entry
=None):
25 if not oid
and log_entry
:
28 commit
= cls
.commits
[oid
]
29 if log_entry
and not commit
.parsed
:
30 commit
.parse(log_entry
)
31 cls
.root_generation
= max(commit
.generation
,
34 commit
= Commit(oid
=oid
,
37 cls
.root_generation
+= 1
38 commit
.generation
= max(commit
.generation
,
40 cls
.commits
[oid
] = commit
44 class DAG(Observable
):
45 ref_updated
= 'ref_updated'
46 count_updated
= 'count_updated'
48 def __init__(self
, ref
, count
):
49 Observable
.__init
__(self
)
54 def set_ref(self
, ref
):
55 changed
= ref
!= self
.ref
58 self
.notify_observers(self
.ref_updated
)
61 def set_count(self
, count
):
62 changed
= count
!= self
.count
65 self
.notify_observers(self
.count_updated
)
68 def set_arguments(self
, args
):
71 if self
.set_count(args
.count
):
72 self
.overrides
['count'] = args
.count
74 if hasattr(args
, 'args') and args
.args
:
75 ref
= core
.list2cmdline(args
.args
)
77 self
.overrides
['ref'] = ref
79 def overridden(self
, opt
):
80 return opt
in self
.overrides
83 all_refs
= utils
.shell_split(self
.ref
)
85 all_refs
= all_refs
[all_refs
.index('--'):]
87 return [p
for p
in all_refs
if p
and core
.exists(p
)]
106 def __init__(self
, oid
=None, log_entry
=None):
116 self
.generation
= CommitFactory
.root_generation
120 self
.parse(log_entry
)
122 def parse(self
, log_entry
, sep
=logsep
):
123 self
.oid
= log_entry
[:40]
124 after_oid
= log_entry
[41:]
125 details
= after_oid
.split(sep
, 5)
126 (parents
, tags
, author
, authdate
, email
, summary
) = details
128 self
.summary
= summary
if summary
else ''
129 self
.author
= author
if author
else ''
130 self
.authdate
= authdate
if authdate
else ''
131 self
.email
= email
if email
else ''
135 for parent_oid
in parents
.split(' '):
136 parent
= CommitFactory
.new(oid
=parent_oid
)
137 parent
.children
.append(self
)
138 if generation
is None:
139 generation
= parent
.generation
+1
140 self
.parents
.append(parent
)
141 generation
= max(parent
.generation
+1, generation
)
142 self
.generation
= generation
145 for tag
in tags
[2:-1].split(', '):
151 def add_label(self
, tag
):
152 """Add tag/branch labels from `git log --decorate ....`"""
154 if tag
.startswith('tag: '):
155 tag
= tag
[5:] # strip off "tag: " leaving refs/tags/
157 if tag
.startswith('refs/'):
158 # strip off refs/ leaving just tags/XXX remotes/XXX heads/XXX
161 if tag
.endswith('/HEAD'):
164 # Git 2.4 Release Notes (draft)
165 # =============================
167 # Backward compatibility warning(s)
168 # ---------------------------------
170 # This release has a few changes in the user-visible output from
171 # Porcelain commands. These are not meant to be parsed by scripts, but
172 # the users still may want to be aware of the changes:
174 # * Output from "git log --decorate" (and "%d" format specifier used in
175 # the userformat "--format=<string>" parameter "git log" family of
176 # command takes) used to list "HEAD" just like other tips of branch
177 # names, separated with a comma in between. E.g.
179 # $ git log --decorate -1 master
180 # commit bdb0f6788fa5e3cacc4315e9ff318a27b2676ff4 (HEAD, master)
183 # This release updates the output slightly when HEAD refers to the tip
184 # of a branch whose name is also shown in the output. The above is
187 # $ git log --decorate -1 master
188 # commit bdb0f6788fa5e3cacc4315e9ff318a27b2676ff4 (HEAD -> master)
191 # C.f. http://thread.gmane.org/gmane.linux.kernel/1931234
193 head_arrow
= 'HEAD -> '
194 if tag
.startswith(head_arrow
):
195 self
.tags
.add('HEAD')
196 self
.add_label(tag
[len(head_arrow
):])
206 'summary': self
.summary
,
207 'author': self
.author
,
208 'authdate': self
.authdate
,
209 'parents': [p
.oid
for p
in self
.parents
],
214 return json
.dumps(self
.data(), sort_keys
=True, indent
=4, default
=list)
217 ''' Returns True if the node is a fork'''
218 return len(self
.children
) > 1
221 ''' Returns True if the node is a fork'''
222 return len(self
.parents
) > 1
225 class RepoReader(object):
227 def __init__(self
, context
, params
):
228 self
.context
= context
230 self
.git
= context
.git
235 '-c', 'log.abbrevCommit=false',
236 '-c', 'log.showSignature=false',
243 """Indicates that all data has been read"""
245 """List of commits objects in topological order"""
247 cached
= property(lambda self
: self
._cached
)
248 """Return True when no commits remain to be read"""
251 return len(self
._topo
_list
)
254 CommitFactory
.reset()
262 """Generator function returns Commit objects found by the params"""
267 yield self
._topo
_list
[idx
]
274 ref_args
= utils
.shell_split(self
.params
.ref
)
275 cmd
= self
._cmd
+ ['-%d' % self
.params
.count
] + ref_args
276 self
._proc
= core
.start_command(cmd
)
279 log_entry
= core
.readline(self
._proc
.stdout
).rstrip()
283 self
.returncode
= self
._proc
.returncode
288 yield self
._objects
[oid
]
290 commit
= CommitFactory
.new(log_entry
=log_entry
)
291 self
._objects
[commit
.oid
] = commit
292 self
._topo
_list
.append(commit
)
296 def __getitem__(self
, oid
):
297 return self
._objects
[oid
]
300 return list(self
._objects
.items())