]> asedeno.scripts.mit.edu Git - git-svn-keywords.git/blob - git-svn-keywords.py
Polish
[git-svn-keywords.git] / git-svn-keywords.py
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 # Copyright (c) 2009 Alejandro R. SedeƱo <asedeno@mit.edu>
5
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:
13
14 # The above copyright notice and this permission notice shall be
15 # included in all copies or substantial portions of the Software.
16
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
24 # SOFTWARE.
25
26 # git svn keyword parsing, populating, and clearing.
27
28 from __future__ import with_statement
29 import errno, os, re, urllib
30 from ConfigParser import ConfigParser
31 from optparse import OptionParser
32 import git
33
34 VERSION = "0.9"
35
36 # Where we keep data in the repo.
37 def gsk(g):
38     return os.path.join(g.path, 'svn_keywords')
39
40 #Configuration Data
41 CONFIG = ConfigParser()
42 FILES = ConfigParser()
43 FILEINFO = ConfigParser()
44
45 CONFIG_PATH = ''
46 FILES_PATH = ''
47 FILEINFO_PATH = ''
48
49
50 # Valid keywords:
51 svn_keywords = {'Date': ['Date', 'LastDateChanged'],
52                 'Revision': ['Revision', 'LastChangedRevision', 'Rev'],
53                 'Author': ['Author','LastChangedBy'],
54                 'URL': ['HeadURL', 'URL'],
55                 'Id': ['Id']
56                 }
57
58 # Regular expressions we'll be using to smudge/clean; created as
59 # needed and cached.
60 svn_keywords_re = {}
61 def get_svn_keyword_re(s):
62     if not s in svn_keywords:
63         raise 'Invalid SVN Keyword'
64     if not s in svn_keywords_re:
65         svn_keywords_re[s] = re.compile('\$(' + ('|'.join(svn_keywords[s])) + ')[^$]*\$')
66     return svn_keywords_re[s]
67
68 # Parse the unhandled log.
69 def parse_svn_unhandled(g):
70     try:
71         os.mkdir(gsk(g))
72     except os.error, e:
73         if e.errno != errno.EEXIST:
74             raise
75
76     ver = -1
77     if CONFIG.has_option('core', 'version'):
78         ver = CONFIG.get('core', 'version')
79
80     lastrev = None
81     if ver == VERSION:
82         FILES.read(FILES_PATH)
83         if CONFIG.has_option('core', 'lastrev'):
84             lastrev = CONFIG.getint('core', 'lastrev')
85
86     with open(g.path + '/svn/git-svn/unhandled.log', 'r') as f:
87         # Compile the regular expressions we'll be using here.
88         re_rev = re.compile("^r(\d+)$")
89         re_keywords = re.compile("^\s+[-+]file_prop: (\S+) svn:keywords ?(\S*)$")
90
91         rev = None
92         for line in f:
93             m = re_rev.match(line)
94             if m:
95                 rev = m.group(1)
96                 continue
97
98             if (lastrev >= int(rev)):
99                 continue
100
101             m = re_keywords.match(line)
102             if m:
103                 path = urllib.unquote(m.group(1))
104                 keywords = set(urllib.unquote(m.group(2)).split(' '))
105                 if not FILES.has_section(path):
106                     FILES.add_section(path)
107                 FILES.set(path, rev, keywords)
108
109         lastrev = max(int(rev), lastrev)
110         CONFIG.set('core', 'lastrev', lastrev)
111         CONFIG.set('core', 'version', VERSION)
112
113     with open(FILES_PATH, 'wb') as f:
114         FILES.write(f)
115
116     with open(CONFIG_PATH, 'wb') as f:
117         CONFIG.write(f)
118
119 def get_path_info(g, path):
120     write_config = False
121
122     # parse ls-tree output and get a blob id for path
123     blob = g.git.ls_tree('HEAD', path).split(' ')[2].split("\t")[0]
124
125     # translate that to a commit id
126     if not CONFIG.has_option('BlobToCommit', blob):
127         CONFIG.set('BlobToCommit', blob, g.commits('HEAD', path, 1)[0].id)
128         write_config = True
129     commit = CONFIG.get('BlobToCommit', blob)
130
131     # tranlsate that into an svn revision id
132     if not CONFIG.has_option('CommitToRev', commit):
133         CONFIG.set('CommitToRev',commit,g.git.svn('find-rev', commit))
134         write_config = True
135     file_rev = CONFIG.get('CommitToRev', commit)
136
137     # get information about that revision
138     info_dict = {}
139     if not CONFIG.has_option('RevInfo', file_rev):
140         for line in g.git.svn('info', path).split("\n"):
141             k, v = line.split(": ", 1)
142             if k == 'Last Changed Date':
143                 info_dict['Date'] = v
144             elif k == 'Last Changed Author':
145                 info_dict['Author'] = v
146         CONFIG.set('RevInfo', file_rev, info_dict)
147         write_config = True
148     else:
149         info = CONFIG.get('RevInfo', file_rev)
150         info_dict.update(info if type(info) is dict else eval(info))
151
152     if write_config:
153         with open(CONFIG_PATH, 'wb') as f:
154             CONFIG.write(f)
155
156     info_dict['Revision'] = file_rev
157     return info_dict
158
159 # Do the work.
160 def smudge(g, options):
161     parse_svn_unhandled(g)
162     rev_head = int(g.git.svn('find-rev', 'HEAD'))
163     url_base = g.git.svn('info', '--url')
164
165     FILES.read(FILES_PATH)
166     FILEINFO.read(FILEINFO_PATH)
167
168     paths = FILES.sections()
169     paths.sort()
170     for path in paths:
171         if not os.path.exists(path):
172             continue
173
174         kw_rev = max(filter(lambda x: x <= rev_head, map(int, FILES.options(path))))
175
176         info_dict = {}
177         if not options.clean:
178             info_dict.update(get_path_info(g, path))
179             info_dict['URL'] = '/'.join([url_base, path])
180             info_dict['Name'] = os.path.basename(path)
181             info_dict['Revision'] = str(max(kw_rev, info_dict['Revision']))
182
183         buf = ''
184         with open(os.path.join(g.wd, path), 'r') as f:
185             buf = f.read()
186
187         keywords = eval(FILES.get(path, str(kw_rev)))
188         for k in keywords:
189             for sk in svn_keywords:
190                 if k in svn_keywords[sk]:
191                     if options.clean:
192                         buf = re.sub(get_svn_keyword_re(sk), '$\\1$', buf)
193                     elif sk == 'Id':
194                         id_str = ' '.join([info_dict['Name'],
195                                            info_dict['Revision'],
196                                            info_dict['Date'],
197                                            info_dict['Author']])
198                         buf = re.sub(get_svn_keyword_re(sk), '$\\1: ' + id_str + ' $', buf)
199                     else:
200                         buf = re.sub(get_svn_keyword_re(sk), '$\\1: ' + info_dict[sk] + ' $', buf)
201
202         with open(os.path.join(g.wd, path), 'w') as f:
203             f.write(buf)
204         if options.verbose:
205             print path + ' [' + ', '.join(keywords) + '] [len: ' + str(len(buf)) +']'
206
207 if __name__ == '__main__':
208
209     parser = OptionParser(version="%prog "+str(VERSION))
210     parser.set_defaults(clean=None)
211     parser.add_option("-s", "--smudge",
212                       action="store_false", dest="clean",
213                       help="Populate svn:keywords.")
214     parser.add_option("-c", "--clean",
215                       action="store_true", dest="clean",
216                       help="Return svn:keywords to pristene state.")
217     parser.add_option("-v", "--verbose",
218                       action="store_true", dest="verbose", default=False)
219     (options, args) = parser.parse_args()
220
221     if (options.clean is None):
222         parser.print_help()
223         exit(0)
224     else:
225         try:
226             g = git.Repo()
227         except git.errors.InvalidGitRepositoryError:
228             print "You are not in a git repository or working directory."
229             exit(1)
230
231         CONFIG_PATH = os.path.join(gsk(g), 'conf.ini')
232         FILES_PATH = os.path.join(gsk(g), 'files.ini')
233         FILEINFO_PATH = os.path.join(gsk(g), 'fileinfo.ini')
234
235         CONFIG.read(CONFIG_PATH)
236         for section in ['core','CommitToRev','BlobToCommit', 'RevInfo']:
237             if not CONFIG.has_section(section):
238                 CONFIG.add_section(section)
239
240         smudge(g, options)