- add 'help' subcommand (and add help :-)
[opensuse:osc.git] / osc / commandline.py
1 #!/usr/bin/python
2
3 # Copyright (C) 2006 Peter Poeml.  All rights reserved.
4 # This program is free software; it may be used, copied, modified
5 # and distributed under the terms of the GNU General Public Licence,
6 # either version 2, or (at your option) any later version.
7
8
9 from core import *
10
11
12 usage_general = """\
13 usage: osc <subcommand> [options] [args]
14 OpenSUSE build service command-line tool, version %s.
15 Type 'osc help <subcommand>' for help on a specific subcommand.
16
17 Most subcommands take file and/or directory arguments, recursing
18 on the directories.  If no arguments are supplied to such a
19 command, it recurses on the current directory (inclusive) by default.
20
21 Available subcommands:
22
23     add
24     addremove
25     checkin (ci)
26     checkout (co)
27     diff
28     help
29     history (hist)
30     id
31     log
32     ls
33     meta
34     platforms
35     remove (del, delete, rm)
36     resolved
37     results
38     results_meta
39     status (st)
40     update (up)
41
42 """ % get_osc_version()
43
44
45 def init(args):
46     """Initialize a directory to be a working copy of an existing buildservice
47 package. (This is the same as checking out a package and then copying sources
48 into the directory. It does NOT create a new package.)
49
50 usage: init <prj> <pac>
51     """
52
53     project = args[0]
54     package = args[1]
55     init_package_dir(project, package, os.path.curdir)
56     print 'Initializing %s (Project: %s, Package: %s)' % (os.curdir, project, package)
57
58
59 def ls(args):
60     """ls (list): List existing content on the server
61
62 usage: ls                         # list projects
63        ls Apache                  # list packages in a project
64        ls Apache subversion       # list files of package of a project
65     """
66
67     if not args:
68         print '\n'.join(get_slash_source())
69     elif len(args) == 1:
70         project = args[0]
71         print '\n'.join(meta_get_packagelist(project))
72     elif len(args) == 2:
73         project = args[0]
74         package = args[1]
75         print '\n'.join(meta_get_filelist(project, package))
76
77
78 def meta(args):
79     """Shows meta information
80
81 usage: meta Apache              # show meta of project 'Apache'
82        meta Apache subversion   # show meta of package 'subversion'
83     """
84
85     if not args:
86         print 'missing argument'
87         print meta.func_doc
88         sys.exit(1)
89
90     if len(args) == 2:
91         project = args[0]
92         package = args[1]
93         print ''.join(show_package_meta(project, package))
94         print ''.join(show_files_meta(project, package))
95
96     elif len(args) == 1:
97         project = args[0]
98         print ''.join(show_project_meta(project))
99
100
101 def diff(args):
102     """diff: Generates a diff, to view the local changes
103
104     usage: 1. osc diff                       # current dir
105            2. osc diff file1 file2 ...
106
107     """
108
109     args = parseargs(args)
110     pacs = findpacs(args)
111
112     for p in pacs:
113         if p.todo == []:
114             for i in p.filenamelist:
115                 s = p.status(i)
116                 if s == 'M' or s == 'C':
117                     p.todo.append(i)
118
119         d = []
120         for filename in p.todo:
121             d.append('Index: %s\n' % filename)
122             d.append('===================================================================\n')
123             d.append(get_source_file_diff(p.dir, filename, p.rev))
124         if d:
125             print ''.join(d)
126
127
128             
129 def checkout(args):
130     """checkout (co): Check out content from the server.
131
132 usage: co Apache                    # entire project
133        co Apache subversion         # a package
134        co Apache subversion foo     # single file -> to current dir
135     """
136
137     project = package = filename = None
138     try: 
139         project = args[0]
140         package = args[1]
141         filename = args[2]
142     except: 
143         pass
144
145     if filename:
146         get_source_file(project, package, filename)
147
148     elif package:
149         checkout_package(project, package)
150
151     else:
152         # all packages
153         for package in meta_get_packagelist(project):
154             checkout_package(project, package)
155
156
157 def status(args):
158     """Show the status (which files have been changed locally)
159 usage: st
160        st <directory>
161        st file1 file2 ...
162     """
163
164     args = parseargs(args)
165     pacs = findpacs(args)
166
167     for p in pacs:
168
169         # no files given as argument? Take all files in current dir
170         if not p.todo:
171             p.todo = p.filenamelist + p.filenamelist_unvers
172
173         for filename in p.todo:
174             s = p.status(filename)
175             if s == 'F':
176                 print statfrmt('!', filename)
177             elif s != ' ':
178                 print statfrmt(s, filename)
179
180
181 def add(args):
182     """Mark files to be added upon next 'checkin'
183
184 usage: add file1 file2 ...
185     """
186
187     if not args:
188         print '%s requires at least one argument' % cmd
189         sys.exit(1)
190
191     filenames = parseargs(args)
192
193     for filename in filenames:
194         if not os.path.exists(filename):
195             print "file '%s' does not exist" % filename
196             sys.exit(1)
197
198     pacs = findpacs(filenames)
199
200     for pac in pacs:
201         for filename in pac.todo:
202             if filename in exclude_stuff:
203                 continue
204
205             pac.addfile(filename)
206             print statfrmt('A', filename)
207
208
209 def addremove(args):
210     """addremove: Adds all new files in local copy and removes all disappeared files.
211
212 usage: addremove
213     """
214
215     args = parseargs(args)
216     pacs = findpacs(args)
217     for p in pacs:
218
219         p.todo = p.filenamelist + p.filenamelist_unvers
220
221         for filename in p.todo:
222             if filename in exclude_stuff:
223                 continue
224             state = p.status(filename)
225             if state == '?':
226                 p.addfile(filename)
227                 print statfrmt('A', filename)
228             elif state == '!':
229                 p.put_on_deletelist(filename)
230                 p.write_deletelist()
231                 os.unlink(os.path.join(p.storedir, filename))
232                 print statfrmt('D', filename)
233
234
235
236 def checkin(args):
237     """checkin (ci): Upload change content from your working copy to the repository
238
239 usage: ci                   # current dir
240        ci <dir>
241        ci file1 file2 ...
242     """
243
244     init_basicauth()
245
246     args = parseargs(args)
247
248     pacs = findpacs(args)
249
250     for p in pacs:
251         p.todo = p.filenamelist_unvers + p.filenamelist
252
253         for filename in p.todo:
254             st = p.status(filename)
255             if st == 'A' or st == 'M':
256                 p.todo_send.append(filename)
257                 print 'Sending        %s' % filename
258             elif st == 'D':
259                 p.todo_delete.append(filename)
260                 print 'Deleting       %s' % filename
261
262         if not p.todo_send and not p.todo_delete:
263             print 'nothing to do for package %s' % p.name
264             continue
265
266         print 'Transmitting file data ', 
267         for filename in p.todo_send:
268             put_source_file(p.prjname, p.name, os.path.join(p.dir, filename))
269             #copy_file(filename, os.path.join(store, filename))
270         for filename in p.todo_delete:
271             del_source_file(p.prjname, p.name, filename)
272             p.to_be_deleted.remove(filename)
273
274         p.update_filesmeta()
275         p.write_deletelist()
276         print
277
278
279 def update(args):
280     """Update a working copy
281
282 usage: up
283        up [pac_dir]         # update a single package by its path
284        up *                 # from within a project dir, update all packages
285        up                   # from within a project dir, update all packages
286                                AND check out all newly added packages
287     """
288
289     args = parseargs(args)
290
291     for arg in args:
292
293         # when 'update' is run inside a project dir, it should...
294         if is_project_dir(arg):
295
296             prj = Project(arg)
297
298             # (a) update all packages
299             for i in prj.pacs_have:
300                 args.append(i)
301
302             # (b) fetch new packages
303             prj.checkout_missing_pacs()
304             args.remove(arg)
305
306
307     pacs = findpacs(args)
308
309     for p in pacs:
310
311         # save filelist and (modified) status before replacing the meta file
312         saved_filenames = p.filenamelist
313         saved_modifiedfiles = []
314         for i in p.filenamelist:
315             if p.status(i) == 'M':
316                 saved_modifiedfiles.append(i)
317         p.update_filesmeta()
318         p = Package(p.dir)
319
320         # which files do no longer exist upstream?
321         disappeared = []
322         for filename in saved_filenames:
323             if filename not in p.filenamelist:
324                 disappeared.append(filename)
325             
326
327         for filename in saved_filenames:
328             if filename in disappeared:
329                 print statfrmt('D', filename)
330                 p.delfile(filename)
331                 continue
332
333         for filename in p.filenamelist:
334
335             state = p.status(filename)
336             if state == 'M' and filename in saved_modifiedfiles:
337                 print 'merging'
338                 status_after_merge = p.mergefile(filename)
339                 print statfrmt(status_after_merge, filename)
340             elif state == 'M':
341                 p.updatefile(filename)
342                 print statfrmt('U', filename)
343             elif state == '!':
344                 p.updatefile(filename)
345                 print 'Restored \'%s\'' % filename
346             elif state == 'F':
347                 p.updatefile(filename)
348                 print statfrmt('A', filename)
349             elif state == ' ':
350                 pass
351
352
353         p.update_pacmeta()
354
355         #print ljust(p.name, 45), 'At revision %s.' % p.rev
356         print 'At revision %s.' % p.rev
357                 
358
359
360         
361 def delete(args):
362     """rm (remove, del, delete): Mark files to be deleted upon next 'checkin'
363
364 usage: rm file1 file2 ...
365     """
366
367     if not args:
368         print 'delete requires at least one argument'
369         sys.exit(1)
370
371     args = parseargs(args)
372     pacs = findpacs(args)
373
374     for p in pacs:
375
376         for filename in p.todo:
377             p.put_on_deletelist(filename)
378             p.write_deletelist()
379             try:
380                 os.unlink(os.path.join(p.dir, filename))
381                 os.unlink(os.path.join(p.storedir, filename))
382             except:
383                 pass
384             print statfrmt('D', filename)
385
386
387 def resolved(args):
388     """If an update can't be merged automatically, a file is in 'C' (conflict)
389 state, and conflicts are marked with special <<<<<<< and >>>>>>> lines. 
390 After manually resolving the problem, use
391
392 usage: resolved <filename>
393 """
394
395     if not args:
396         print 'this command requires at least one argument'
397         sys.exit(1)
398
399     args = parseargs(args)
400     pacs = findpacs(args)
401
402     for p in pacs:
403
404         for filename in p.todo:
405             print "Resolved conflicted state of '%s'" % filename
406             p.clear_from_conflictlist(filename)
407
408
409 def userid(args):
410     """id:  show metadata about user <userid>
411
412 usage: id <userid>
413     """
414
415     if not args:
416         print 'this command requires at least one argument'
417         sys.exit(1)
418
419     r = get_user_id(args[0])
420     if r:
421         print ''.join(r)
422
423
424 def platforms(args):
425     """platforms: Shows platforms
426
427 usage 1. platforms
428             Shows available platforms/build targets
429
430       2. platforms <project>
431             Shows the configured platforms/build targets of a project
432     """
433
434     if args:
435         project = args[0]
436         print '\n'.join(get_platforms_of_project(project))
437     else:
438         print '\n'.join(get_platforms())
439
440
441 def results_meta(args):
442     """Shows the build results of the package in raw XML
443
444 usage: results_meta [platform]
445     """
446     wd = os.curdir
447     package = store_read_package(wd)
448     project = store_read_project(wd)
449     if args:
450         platform = args[0]
451         print ''.join(show_results_meta(project, package, platform))
452     else:
453         for platform in get_platforms_of_project(project):
454             print ''.join(show_results_meta(project, package, platform))
455
456             
457 def results(args):
458     """Shows the build results of a package
459
460 usage: 1. results                   # package = current dir
461        2. results <packagedir>
462     """
463
464     if args and len(args) > 1:
465         print 'getting results for more than one package is not supported'
466         print sys.exit(1)
467         
468     if args:
469         wd = args[0]
470     else:
471         wd = os.curdir
472     package = store_read_package(wd)
473     project = store_read_project(wd)
474
475     for platform in get_platforms_of_project(project):
476         print '\n'.join(get_results(project, package, platform))
477
478             
479 def log(args):
480     """log: Shows the log file from a package (you need to be inside a package directory)
481
482 usage: log <platform> <arch>
483     """
484     wd = os.curdir
485     package = store_read_package(wd)
486     project = store_read_project(wd)
487
488     platform = args[0]
489     arch = args[1]
490     print ''.join(get_log(project, package, platform, arch))
491
492
493 def history(args):
494     """history: Shows the build history of a package (NOT IMPLEMENTED YET)
495
496 usage: history <pacdir>
497     """
498     args = parseargs(args)
499     pacs = findpacs(args)
500
501     for p in pacs:
502         print ''.join(get_history(p.prjname, p.name))
503
504
505 def help(args):
506     """help: Describe the usage of this program or its subcommands.
507
508 usage: help [SUBCOMMAND...]
509     """
510     if args:
511         cmd = resolve_cmd_alias(args[0])
512
513         try:
514             print cmd_dict[cmd].func_doc
515
516         except KeyError:
517             print 'unknown command \'%s\'' % cmd
518             sys.exit(1)
519     else:
520         print usage_general
521
522
523 def resolve_cmd_alias(cmd):
524     if cmd == 'ci': return 'checkin'
525     if cmd == 'co': return 'checkout'
526     if cmd == 'st': return 'status'
527     if cmd == 'up': return 'update'
528     if cmd == 'list': return 'ls'
529     if cmd == 'hist': return 'history'
530     if cmd in ['del', 'remove', 'rm']: return 'delete'
531     return cmd
532     
533
534 cmd_dict = {
535     'add':          add,
536     'addremove':    addremove,
537     'checkin':      checkin,
538     'checkout':     checkout,
539     'diff':         diff,
540     'help':         help,
541     'history':      history,
542     'id':           userid,         # <- small difference here
543     'init':         init,           # depracated
544     'log':          log,
545     'ls':           ls,
546     'meta':         meta,
547     'platforms':    platforms,
548     'delete':       delete,
549     'resolved':     resolved,
550     'results':      results,
551     'results_meta': results_meta,
552     'status':       status,
553     'update':       update,
554 }
555
556 def main():
557     """handling of commandline arguments, and dispatching to subcommands"""
558
559     # which subcommand?
560     if len(sys.argv) < 2:
561         print "Type 'osc help' for usage."
562         sys.exit(0)
563
564     cmd = resolve_cmd_alias(sys.argv[1])
565
566     # more arguments?
567     if len(sys.argv) > 2:
568         args = sys.argv[2:]
569     else:
570         args = None
571
572     # run subcommand
573     cmd_dict[cmd](args)
574
575
576 if __name__ == '__main__':
577     init_basicauth()
578     main()
579