2 # -*- coding: utf-8 -*-
4 # Copyright (c) 2009 Alejandro R. SedeƱo <asedeno@mit.edu>
6 # Permission is hereby granted, free of charge, to any person
7 # obtaining a copy of this software and associated documentation files
8 # (the "Software"), to deal in the Software without restriction,
9 # including without limitation the rights to use, copy, modify, merge,
10 # publish, distribute, sublicense, and/or sell copies of the Software,
11 # and to permit persons to whom the Software is furnished to do so,
12 # subject to the following conditions:
14 # The above copyright notice and this permission notice shall be
15 # included in all copies or substantial portions of the Software.
17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
21 # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
22 # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
23 # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26 # git svn keyword parsing, populating, and clearing.
28 from __future__ import with_statement
29 import errno, os, re, urllib
30 from ConfigParser import ConfigParser
31 from optparse import OptionParser
32 from fnmatch import fnmatch
37 # Where we keep data in the repo.
39 return os.path.join(g.path, 'svn_keywords')
42 CONFIG = ConfigParser()
43 FILES = ConfigParser()
44 FILEINFO = ConfigParser()
52 svn_keywords = {'Date': ['Date', 'LastDateChanged'],
53 'Revision': ['Revision', 'LastChangedRevision', 'Rev'],
54 'Author': ['Author','LastChangedBy'],
55 'URL': ['HeadURL', 'URL'],
59 # Regular expressions we'll be using to smudge/clean; created as
62 def get_svn_keyword_re(s):
63 if not s in svn_keywords:
64 raise 'Invalid SVN Keyword'
65 if not s in svn_keywords_re:
66 svn_keywords_re[s] = re.compile('\$(' + ('|'.join(svn_keywords[s])) + ')[^$]*\$')
67 return svn_keywords_re[s]
69 def conf_right_version():
71 if CONFIG.has_option('core', 'version'):
72 ver = CONFIG.get('core', 'version')
76 if conf_right_version():
77 FILES.read(FILES_PATH)
79 def get_last_rev(path):
80 if not CONFIG.has_section(path):
81 CONFIG.add_section(path)
84 if conf_right_version() and CONFIG.has_option(path, 'lastrev'):
86 lastrev = CONFIG.getint(path, 'lastrev')
92 # Parse the unhandled log.
93 def _do_parse_unhandled(directory):
94 base = os.path.join(directory)
95 for d in os.listdir(base):
96 subent = os.path.join(base, d)
97 if (d == 'unhandled.log' and os.path.isfile(subent)):
99 strip_prefix = g.git.config('--get','svn-remote.svn.fetch').split(':')[0]
100 lastrev = get_last_rev(subent)
101 with open(subent, 'r') as f:
102 # Compile the regular expressions we'll be using here.
103 re_rev = re.compile("^r(\d+)$")
104 re_keywords = re.compile("^\s+[-+]file_prop: (\S+) svn:keywords ?(\S*)$")
107 m = re_rev.match(line)
112 if (lastrev >= int(rev)):
115 m = re_keywords.match(line)
117 path = urllib.unquote(m.group(1))
118 path = os.path.relpath(path, strip_prefix)
119 keywords = set(urllib.unquote(m.group(2)).split(' '))
120 if not FILES.has_section(path):
121 FILES.add_section(path)
122 FILES.set(path, rev, keywords)
124 lastrev = max(int(rev), lastrev)
125 CONFIG.set(subent, 'lastrev', lastrev)
126 elif (os.path.isdir(subent)):
127 _do_parse_unhandled(subent)
129 def parse_svn_unhandled(g):
133 if e.errno != errno.EEXIST:
136 _do_parse_unhandled(os.path.join(g.path, 'svn'))
137 CONFIG.set('core', 'version', VERSION)
139 with open(FILES_PATH, 'wb') as f:
142 with open(CONFIG_PATH, 'wb') as f:
145 def get_path_info(g, path):
148 # parse ls-tree output and get a blob id for path
149 blob = g.git.ls_tree('HEAD', path).split(' ')[2].split("\t")[0]
151 # translate that to a commit id
152 if not CONFIG.has_option('BlobToCommit', blob):
153 CONFIG.set('BlobToCommit', blob, g.commits('HEAD', path, 1)[0].id)
155 commit = CONFIG.get('BlobToCommit', blob)
157 # tranlsate that into an svn revision id
158 if not CONFIG.has_option('CommitToRev', commit):
159 file_rev = g.git.svn('find-rev', commit)
161 file_rev = "%iM" % find_last_svn_rev('HEAD')
162 CONFIG.set('CommitToRev', commit, file_rev)
165 file_rev = CONFIG.get('CommitToRev', commit)
167 # get information about that revision
170 if not CONFIG.has_option('RevInfo', file_rev):
171 for line in g.git.svn('info', path).split("\n"):
172 k, v = line.split(": ", 1)
173 if k == 'Last Changed Date':
174 info_dict['Date'] = v
175 elif k == 'Last Changed Author':
176 info_dict['Author'] = v
177 CONFIG.set('RevInfo', file_rev, info_dict)
180 info = CONFIG.get('RevInfo', file_rev)
181 info_dict.update(info if type(info) is dict else eval(info))
184 with open(CONFIG_PATH, 'wb') as f:
187 info_dict['Revision'] = file_rev
190 def find_last_svn_rev(treeish, parent=0):
191 svnRev = g.git.svn('find-rev', "%s~%i" % (treeish, parent))
195 return find_last_svn_rev(treeish, parent+1)
198 def smudge(g, options):
200 parse_svn_unhandled(g)
201 rev_head = find_last_svn_rev('HEAD')
202 url_base = g.git.svn('info', '--url')
204 FILES.read(FILES_PATH)
205 FILEINFO.read(FILEINFO_PATH)
208 with open(os.path.join(g.wd,'.git','info','exclude')) as f:
211 ignores.append(line.rstrip('\n'))
213 paths = FILES.sections()
216 if not os.path.exists(path):
226 kw_rev = max(filter(lambda x: x <= rev_head, map(int, FILES.options(path))))
231 if not options.clean:
232 info_dict.update(get_path_info(g, path))
233 info_dict['URL'] = '/'.join([url_base, path])
234 info_dict['Name'] = os.path.basename(path)
235 info_dict['Revision'] = str(max(kw_rev, info_dict['Revision']))
238 with open(os.path.join(g.wd, path), 'r') as f:
241 keywords = eval(FILES.get(path, str(kw_rev)))
243 for sk in svn_keywords:
244 if k in svn_keywords[sk]:
246 buf = re.sub(get_svn_keyword_re(sk), '$\\1$', buf)
248 id_str = ' '.join([info_dict['Name'],
249 info_dict['Revision'],
251 info_dict['Author']])
252 buf = re.sub(get_svn_keyword_re(sk), '$\\1: ' + id_str + ' $', buf)
254 buf = re.sub(get_svn_keyword_re(sk), '$\\1: ' + info_dict[sk] + ' $', buf)
256 with open(os.path.join(g.wd, path), 'w') as f:
259 print path + ' [' + ', '.join(keywords) + '] [len: ' + str(len(buf)) +']'
261 if __name__ == '__main__':
263 parser = OptionParser(version="%prog "+str(VERSION))
264 parser.set_defaults(clean=None)
265 parser.add_option("-s", "--smudge",
266 action="store_false", dest="clean",
267 help="Populate svn:keywords.")
268 parser.add_option("-c", "--clean",
269 action="store_true", dest="clean",
270 help="Return svn:keywords to pristene state.")
271 parser.add_option("-v", "--verbose",
272 action="store_true", dest="verbose", default=False)
273 (options, args) = parser.parse_args()
275 if (options.clean is None):
281 except git.errors.InvalidGitRepositoryError:
282 print "You are not in a git repository or working directory."
286 print "This appears to be a bare git repository."
291 CONFIG_PATH = os.path.join(gsk(g), 'conf.ini')
292 FILES_PATH = os.path.join(gsk(g), 'files.ini')
293 FILEINFO_PATH = os.path.join(gsk(g), 'fileinfo.ini')
295 CONFIG.read(CONFIG_PATH)
296 for section in ['core','CommitToRev','BlobToCommit', 'RevInfo']:
297 if not CONFIG.has_section(section):
298 CONFIG.add_section(section)