1 | | """ |
2 | | Darcs from Sage |
3 | | |
4 | | These functions make setup and use of darcs with Sage easier. |
5 | | """ |
6 | | |
7 | | ######################################################################## |
8 | | # Copyright (C) 2006 William Stein <wstein@gmail.com> |
9 | | # |
10 | | # Distributed under the terms of the GNU General Public License (GPL) |
11 | | # |
12 | | # http://www.gnu.org/licenses/ |
13 | | ######################################################################## |
14 | | |
15 | | |
16 | | import os, shutil |
17 | | |
18 | | import package |
19 | | |
20 | | PAGER='less' # more doesn't work with darcs! |
21 | | |
22 | | def darcs_install(): |
23 | | """ |
24 | | Download and install a statically binary darcs executable. |
25 | | |
26 | | This works on Cygwin, OS X, and intel-based Linux. If you |
27 | | are using something else, obtain darcs yourself and put |
28 | | it anywhere in your PATH. |
29 | | """ |
30 | | uname = os.uname()[0] |
31 | | if uname[:6] == 'CYGWIN': |
32 | | package.install_package('darcs_cygwin', force=True) |
33 | | elif uname == 'Darwin': |
34 | | if os.uname()[4] == 'i386': |
35 | | package.install_package('darcs_mactel') |
36 | | else: |
37 | | package.install_package('darcs_darwin') |
38 | | elif uname == 'Linux': |
39 | | package.install_package('darcs_linux') |
40 | | else: |
41 | | raise RuntimeError, "No Sage darcs package available for your platform (this just means you need to get a darcs binary yourself and put it somewhere in your PATH). See http://darcs.net/DarcsWiki/CategoryBinaries" |
42 | | |
43 | | known_installed = False |
44 | | def darcs_ensure_installed(): |
45 | | """ |
46 | | Download and install darcs into your Sage environment, if |
47 | | you do not already have darcs in your PATH. |
48 | | |
49 | | The darcs binary is put in <SAGE_ROOT>/bin. |
50 | | """ |
51 | | global known_installed |
52 | | if known_installed: |
53 | | return |
54 | | if os.system('darcs --help 1>/dev/null 2>/dev/null') == 0: |
55 | | known_installed = True |
56 | | return |
57 | | darcs_install() |
58 | | |
59 | | class Darcs: |
60 | | r""" |
61 | | This is a darcs repository. |
62 | | |
63 | | If you try to use it and don't have darcs installed on your computer, |
64 | | darcs will be automatically downloaded and installed (assuming you |
65 | | have a net connection). Also, you do not have to explicitly initialize |
66 | | your repository in order to use it. E.g., if you do |
67 | | \code{darcs_src.changes()} |
68 | | and you've never used darcs before, darcs will be downloaded and |
69 | | installed, then the latest source repository will be downloaded |
70 | | and installed. |
71 | | |
72 | | The few of the simplest and most useful commands are directly |
73 | | provided as member functions. However, you can use the full |
74 | | functionality of darcs by noting that typing, e.g., |
75 | | \code{darcs_src("command line arguments")} |
76 | | is \emph{exactly} the same as typing |
77 | | \code{cd repo_directory && darcs command line arguments | less} |
78 | | """ |
79 | | def __init__(self, dir, name, url, target=None): |
80 | | """ |
81 | | INPUT: |
82 | | dir -- directory that will contain the repository |
83 | | name -- a friendly name for the repository (only used for printing) |
84 | | url -- a default URL to pull or record sends against (e.g., |
85 | | this could be a master repository on modular.math.washington.edu) |
86 | | target -- if the last part of dir is, e.g., sage-darcs, |
87 | | create a symlink from sage-darcs to target. |
88 | | If target=None, this symlink will not be created. |
89 | | """ |
90 | | self.__dir = os.path.abspath(dir) |
91 | | self.__name = name |
92 | | self.__url = url |
93 | | self.__initialized = False |
94 | | self.__target = target |
95 | | |
96 | | def __repr__(self): |
97 | | return "Darcs repository '%s' in directory %s"%(self.__name, |
98 | | self.__dir) |
99 | | |
100 | | def __call__(self, cmd, check_initialized=True): |
101 | | """ |
102 | | Run 'darcs cmd' where cmd is an arbitrary string |
103 | | in the darcs repository. |
104 | | """ |
105 | | darcs_ensure_installed() |
106 | | if check_initialized and not self.__initialized: |
107 | | self.initialize() |
108 | | darcs_ensure_installed() |
109 | | s = 'cd "%s" && darcs %s'%(self.__dir, cmd) |
110 | | print s |
111 | | return os.system(s) |
112 | | |
113 | | def apply(self, patchfile, options=''): |
114 | | """ |
115 | | Apply patches from a darcs patch to the repository. |
116 | | """ |
117 | | patchfile = os.path.abspath(patchfile) |
118 | | print "Applying patchfile %s"%patchfile |
119 | | self('apply %s "%s"'%(options, patchfile)) |
120 | | |
121 | | def add(self, files, options=''): |
122 | | """ |
123 | | Add the given list of files (or file) or directories |
124 | | to your DARCS repository. |
125 | | |
126 | | Add needs to be called whenever you add a new file or |
127 | | directory to your project. Of course, it also needs to be |
128 | | called when you first create the project, to let darcs know |
129 | | which files should be kept track of. |
130 | | |
131 | | INPUT: |
132 | | files -- list or string; name of file or directory. |
133 | | options -- string |
134 | | """ |
135 | | if isinstance(files, str): |
136 | | files = [files] |
137 | | for file in files: |
138 | | file = os.path.abspath(file) |
139 | | print "Adding file %s"%file |
140 | | self('add %s "%s"'%(options, file)) |
141 | | |
142 | | def get(self): |
143 | | if self.__initialized: |
144 | | print "not getting -- already initialized" |
145 | | D = self.__dir.rstrip('/') |
146 | | D_head, D_tail = os.path.split(D) |
147 | | s = 'cd "%s" && darcs get %s'%(D_head, self.__url) |
148 | | print s |
149 | | n = os.system(s) |
150 | | p_head, p_tail = os.path.split(self.__url.rstrip('/')) |
151 | | if p_tail != D_tail: |
152 | | src = '%s/%s'%(os.path.abspath(D_head), p_tail) |
153 | | target = '%s/%s'%(D_head, D_tail) |
154 | | cmd = 'mv "%s/"* "%s"; rmdir "%s"'%(src, target, src) |
155 | | print cmd |
156 | | os.system(cmd) |
157 | | os.system('cd "%s"; chmod +x sage-*'%target) |
158 | | if n: |
159 | | self.__initialized = True |
160 | | |
161 | | def remove(self, files, options=''): |
162 | | """ |
163 | | Remove the given list of files (or file) or directories |
164 | | from your DARCS repository. |
165 | | |
166 | | Remove should be called when you want to remove a file from |
167 | | your project, but don't actually want to delete the file. |
168 | | Otherwise just delete the file or directory, and darcs will |
169 | | notice that it has been removed. Be aware that the file WILL |
170 | | be deleted from any other copy of the repository to which you |
171 | | later apply the patch. |
172 | | |
173 | | INPUT: |
174 | | files -- list or string; name of file or directory. |
175 | | options -- string |
176 | | """ |
177 | | if isinstance(files, str): |
178 | | files = [files] |
179 | | for file in files: |
180 | | file = os.path.abspath(file) |
181 | | print "Removing file %s"%file |
182 | | self('remove %s "%s"'%(options, file)) |
183 | | |
184 | | def changes(self, options=''): |
185 | | """ |
186 | | Display the change log for this repository. |
187 | | """ |
188 | | self('changes %s | %s'%(options, PAGER)) |
189 | | |
190 | | def diff(self, options=''): |
191 | | """ |
192 | | Display the diffs from previous versions. |
193 | | |
194 | | A useful option is '--last [n]'. |
195 | | """ |
196 | | self('diff %s | %s'%(options, PAGER)) |
197 | | |
198 | | def dir(self): |
199 | | """ |
200 | | Return the directory where this repository is located. |
201 | | """ |
202 | | return self.__dir |
203 | | |
204 | | def url(self): |
205 | | """ |
206 | | Return the default 'master url' for this repository. |
207 | | """ |
208 | | return self.__url |
209 | | |
210 | | def help(self, cmd=''): |
211 | | r""" |
212 | | Return help about the given command, or if cmd is omitted |
213 | | a list of commands. |
214 | | |
215 | | If this darcs object is called darcs_src, then you |
216 | | call a command using |
217 | | \code{darcs_src('usual darcs command line notation')} |
218 | | """ |
219 | | self('%s --help | %s'%(cmd, PAGER)) |
220 | | |
221 | | def initialize(self, force=False): |
222 | | """ |
223 | | Create and initialize this darcs repository if you have not |
224 | | already done so. |
225 | | """ |
226 | | if force or not os.path.exists('%s/_darcs'%self.__dir): |
227 | | print "Creating a new darcs repository! %s"%self.__name |
228 | | self.get() |
229 | | ## if self('initialize', check_initialized=False): |
230 | | ## print "WARNING -- problem initializing repository." |
231 | | ## print "Try calling initialize again with the force option?" |
232 | | ## return |
233 | | ## if self.pull('-a -v'): |
234 | | ## print "WARNING -- problem pulling repository." |
235 | | ## print "Try calling initialize again with the force option?" |
236 | | ## return |
237 | | D = self.__dir |
238 | | n = D.split('/')[-1] |
239 | | if not self.__target is None: |
240 | | e = os.system('cd "%s/.." && mv sage sage.old && ln -sn %s %s'%(D, n, self.__target)) |
241 | | if e: |
242 | | raise RuntimeError, "error setting up darcs repository location. Consider deleting or moving %s/sage*"%D |
243 | | if os.path.exists('%s/install'%D): |
244 | | # Darcs pull doesn't preserve permissions. |
245 | | os.system('cd %s; chmod a+x */install 2>/dev/null'%D) |
246 | | os.system('cd %s; chmod +x spkg-* rebuild mirror ref/update* 2>/dev/null'%D) |
247 | | self.__initialized = True |
248 | | if self.__target == 'sage': |
249 | | print "" |
250 | | print "Now building the new Sage libraries" |
251 | | os.system('sage -b') |
252 | | print "If there were any changes, then you must restart Sage in order for the changes to take effect." |
253 | | |
254 | | def pull(self, options='', url=None): |
255 | | """ |
256 | | Pull all new patches from the repository at the given url, |
257 | | or use the default 'official' repository if no url is |
258 | | specified. |
259 | | """ |
260 | | if url is None: |
261 | | url = self.__url |
262 | | self('pull %s %s'%(options, url)) |
263 | | if self.__target == 'sage': |
264 | | print "" |
265 | | print "Now building the new Sage libraries" |
266 | | os.system('sage -b') |
267 | | print "You *MUST* restart Sage in order for the changes to take effect!" |
268 | | |
269 | | |
270 | | def record(self, options=''): |
271 | | """ |
272 | | Interactively record changes as patches between your working |
273 | | copy and your repository. |
274 | | |
275 | | It's OK to hit control-c and restart if something goes wrong. |
276 | | """ |
277 | | print "NOTE -- if you create new files you must first add them" |
278 | | print "with the add method." |
279 | | self('record %s'%options) |
280 | | |
281 | | def unrecord(self, options=''): |
282 | | """ |
283 | | Remove recorded patches without changing the working copy. |
284 | | """ |
285 | | self('unrecord %s'%options) |
286 | | |
287 | | def send(self, filename, options='', url=None): |
288 | | """ |
289 | | Create a darcs patch bundle with the given filename |
290 | | against the repository at the given URL (which is |
291 | | by default the 'official' Sage repository). |
292 | | |
293 | | This is a file that you should probably post to |
294 | | sage-devel@lists.sourceforge.net. It will |
295 | | be written to the current directory. |
296 | | |
297 | | NOTE: The darcs 'send' command by default tries to email |
298 | | patches. Since email rarely works on users personal machines, |
299 | | in Sage the default is to create a file. |
300 | | """ |
301 | | if url is None: |
302 | | url = self.__url |
303 | | # We write to a local tmp file, then move, since under |
304 | | # windows darcs has a bug that makes it fail to write |
305 | | # to any filename that is at all complicated! |
306 | | filename = os.path.abspath(filename) + '.darcs' |
307 | | print 'Writing to %s'%filename |
308 | | tmpfile = '%s/tmpdarcs'%self.__dir |
309 | | if os.path.exists(tmpfile): |
310 | | os.unlink(tmpfile) |
311 | | self('send %s -o tmpdarcs %s'%(options, url)) |
312 | | if os.path.exists(tmpfile): |
313 | | shutil.move(tmpfile, filename) |
314 | | print 'Successfully created darcs patch bundle %s'%filename |
315 | | else: |
316 | | print 'Problem creating darcs patch bundle %s'%filename |
317 | | |
318 | | save = send |
319 | | |
320 | | def what(self, options=''): |
321 | | """ |
322 | | Show all changes between your local darcs repository and |
323 | | the working copy of your source code. |
324 | | """ |
325 | | self('what %s | %s'%(options, PAGER)) |
326 | | |
327 | | |
328 | | ############################################################# |
329 | | # Create the default Sage darcs repositories. |
330 | | ############################################################# |
331 | | |
332 | | #darcs_src = Darcs('%s/devel/sage-darcs'%os.environ['SAGE_ROOT'], |
333 | | # 'Sage source code', |
334 | | # url="http://modular.math.washington.edu/sage/dist/src/sage-darcs", |
335 | | # target='sage') |
336 | | |
337 | | class Deprecated: |
338 | | def __init__(self, newcmd, note=''): |
339 | | self.newcmd = newcmd |
340 | | self.note = note |
341 | | def __repr__(self): |
342 | | return "Use of darcs in Sage is deprecated. Use %s instead. %s"%(self.newcmd, self.note) |
343 | | def pull(self, **args): |
344 | | return str(self) |
345 | | get = pull |
346 | | apply = pull |
347 | | add = pull |
348 | | remove = pull |
349 | | changes = pull |
350 | | diff = pull |
351 | | dir = pull |
352 | | url = pull |
353 | | help = pull |
354 | | initialize = pull |
355 | | record = pull |
356 | | unrecord = pull |
357 | | send = pull |
358 | | what = pull |
359 | | |
360 | | darcs_src = Deprecated('hg_sage', 'Note -- it is not hg_src!') |
361 | | |
362 | | darcs_doc = Deprecated('hg_doc') |
363 | | |
364 | | darcs_scripts = Deprecated('hg_scripts') |
365 | | |
366 | | Darcs('%s/devel/doc-darcs'%os.environ['SAGE_ROOT'], |
367 | | 'Sage documentation', |
368 | | url="http://modular.math.washington.edu/sage/dist/src/doc-darcs", |
369 | | target='doc') |
370 | | |
371 | | darcs_scripts = Darcs('%s/local/bin/'%os.environ['SAGE_ROOT'], |
372 | | 'Sage scripts', |
373 | | url="http://modular.math.washington.edu/sage/dist/src/scripts-darcs", |
374 | | target=None) |