- bump version (0.7)
[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 """ % get_osc_version()
23
24
25 def init(args):
26     """Initialize a directory to be a working copy of an existing buildservice
27 package. (This is the same as checking out a package and then copying sources
28 into the directory. It does NOT create a new package.)
29
30 usage: osc init <prj> <pac>
31     """
32
33     project = args[0]
34     package = args[1]
35     init_package_dir(project, package, os.path.curdir)
36     print 'Initializing %s (Project: %s, Package: %s)' % (os.curdir, project, package)
37
38
39 def ls(args):
40     """ls (list): List existing content on the server
41
42 usage: osc ls                         # list projects
43        ls Apache                  # list packages in a project
44        ls Apache subversion       # list files of package of a project
45     """
46
47     if not args:
48         print '\n'.join(get_slash_source())
49     elif len(args) == 1:
50         project = args[0]
51         print '\n'.join(meta_get_packagelist(project))
52     elif len(args) == 2:
53         project = args[0]
54         package = args[1]
55         print '\n'.join(meta_get_filelist(project, package))
56
57
58 def meta(args):
59     """Shows meta information
60
61 usage: osc meta Apache              # show meta of project 'Apache'
62        osc meta Apache subversion   # show meta of package 'subversion'
63     """
64
65     if not args:
66         print 'missing argument'
67         print meta.func_doc
68         sys.exit(1)
69
70     if len(args) == 2:
71         project = args[0]
72         package = args[1]
73         print ''.join(show_package_meta(project, package))
74         print ''.join(show_files_meta(project, package))
75
76     elif len(args) == 1:
77         project = args[0]
78         print ''.join(show_project_meta(project))
79
80
81 def editmeta(args):
82     """Edit project/package meta information
83 If the named project or package does not exist, it will be created.
84
85 usage: osc editmeta FooPrj              # edit meta of project 'FooPrj'
86        osc editmeta FooPrj barpackage   # edit meta of package 'barpackage'
87     """
88
89     if not args:
90         print 'missing argument'
91         print meta.func_doc
92         sys.exit(1)
93
94     if len(args) == 2:
95         project = args[0]
96         package = args[1]
97         edit_meta(project, package)
98
99     elif len(args) == 1:
100         project = args[0]
101         edit_meta(project, None)
102
103
104 def updatepacmetafromspec(args):
105     """Update package meta information from a specfile
106
107 usage: 1. osc updatepacmetafromspec                       # current dir
108        2. osc updatepacmetafromspec dir1 dir2 ...
109     """
110     args = parseargs(args)
111     pacs = findpacs(args)
112
113     for p in pacs:
114
115         p.read_meta_from_spec()
116         p.update_pac_meta()
117
118
119 def diff(args):
120     """diff: Generates a diff, to view the local changes
121
122     usage: 1. osc diff                       # current dir
123            2. osc diff file1 file2 ...
124
125     """
126
127     args = parseargs(args)
128     pacs = findpacs(args)
129
130     for p in pacs:
131         if p.todo == []:
132             for i in p.filenamelist:
133                 s = p.status(i)
134                 if s == 'M' or s == 'C':
135                     p.todo.append(i)
136
137         d = []
138         for filename in p.todo:
139             d.append('Index: %s\n' % filename)
140             d.append('===================================================================\n')
141             d.append(get_source_file_diff(p.dir, filename, p.rev))
142         if d:
143             print ''.join(d)
144
145
146             
147 def repourls(args):
148     """repourls: shows URLs on which to access the .repos files
149
150 usage: 1. osc repourls
151        2. osc repourls [dir1] [dir2] ...
152
153     """
154
155     args = parseargs(args)
156     pacs = findpacs(args)
157
158     url_tmpl = 'http://software.opensuse.org/download/%s/%s/%s.repo'
159     for p in pacs:
160         platforms = get_platforms_of_project(p.prjname)
161         for platform in platforms:
162             print url_tmpl % (p.prjname.replace(':', ':/'), platform, p.prjname)
163
164
165             
166 def checkout(args):
167     """checkout (co): Check out content from the server.
168
169 usage: osc co Apache                    # entire project
170        osc co Apache subversion         # a package
171        osc co Apache subversion foo     # single file -> to current dir
172     """
173
174     project = package = filename = None
175     try: 
176         project = args[0]
177         package = args[1]
178         filename = args[2]
179     except: 
180         pass
181
182     if filename:
183         get_source_file(project, package, filename)
184
185     elif package:
186         checkout_package(project, package)
187
188     else:
189         # all packages
190         for package in meta_get_packagelist(project):
191             checkout_package(project, package)
192
193
194 def status(args):
195     """Show the status (which files have been changed locally)
196 usage: osc st
197        osc st <directory>
198        osc st file1 file2 ...
199     """
200
201     args = parseargs(args)
202     pacs = findpacs(args)
203
204     for p in pacs:
205
206         # no files given as argument? Take all files in current dir
207         if not p.todo:
208             p.todo = p.filenamelist + p.filenamelist_unvers
209
210         for filename in p.todo:
211             s = p.status(filename)
212             if s == 'F':
213                 print statfrmt('!', filename)
214             elif s != ' ':
215                 print statfrmt(s, filename)
216
217
218 def add(args):
219     """Mark files to be added upon next 'checkin'
220
221 usage: osc add file1 file2 ...
222     """
223
224     if not args:
225         print '%s requires at least one argument' % cmd
226         sys.exit(1)
227
228     filenames = parseargs(args)
229
230     for filename in filenames:
231         if not os.path.exists(filename):
232             print "file '%s' does not exist" % filename
233             sys.exit(1)
234
235     pacs = findpacs(filenames)
236
237     for pac in pacs:
238         for filename in pac.todo:
239             if filename in exclude_stuff:
240                 continue
241
242             pac.addfile(filename)
243             print statfrmt('A', filename)
244
245
246 def addremove(args):
247     """addremove: Adds all new files in local copy and removes all disappeared files.
248
249 usage: osc addremove
250     """
251
252     args = parseargs(args)
253     pacs = findpacs(args)
254     for p in pacs:
255
256         p.todo = p.filenamelist + p.filenamelist_unvers
257
258         for filename in p.todo:
259             if filename in exclude_stuff:
260                 continue
261             state = p.status(filename)
262             if state == '?':
263                 p.addfile(filename)
264                 print statfrmt('A', filename)
265             elif state == '!':
266                 p.put_on_deletelist(filename)
267                 p.write_deletelist()
268                 os.unlink(os.path.join(p.storedir, filename))
269                 print statfrmt('D', filename)
270
271
272
273 def commit(args):
274     """commit (ci): Upload change content from your working copy to the repository
275
276 usage: osc ci                   # current dir
277        osc ci <dir>
278        osc ci file1 file2 ...
279     """
280
281     init_basicauth()
282
283     args = parseargs(args)
284
285     pacs = findpacs(args)
286
287     for p in pacs:
288
289         # commit only if the upstream revision is the same as the working copy's
290         upstream_rev = show_upstream_rev(p.prjname, p.name)
291         if p.rev != upstream_rev:
292             print 'Working copy \'%s\' is out of date (rev %s vs rev %s).' % (p.absdir, p.rev, upstream_rev)
293             print 'Looks as if you need to update it first.'
294             sys.exit(1)
295
296         p.todo = p.filenamelist_unvers + p.filenamelist
297
298         for filename in p.todo:
299             st = p.status(filename)
300             if st == 'A' or st == 'M':
301                 p.todo_send.append(filename)
302                 print 'Sending        %s' % filename
303             elif st == 'D':
304                 p.todo_delete.append(filename)
305                 print 'Deleting       %s' % filename
306
307         if not p.todo_send and not p.todo_delete:
308             print 'nothing to do for package %s' % p.name
309             continue
310
311         print 'Transmitting file data ', 
312         for filename in p.todo_send:
313             sys.stdout.write('.')
314             p.put_source_file(filename)
315         for filename in p.todo_delete:
316             p.delete_source_file(filename)
317             p.to_be_deleted.remove(filename)
318
319         p.update_filesmeta()
320         p.write_deletelist()
321         print
322
323
324 def update(args):
325     """Update a working copy
326
327 usage: osc up
328        osc up [pac_dir]         # update a single package by its path
329        osc up *                 # from within a project dir, update all packages
330        osc up                   # from within a project dir, update all packages
331                                AND check out all newly added packages
332     """
333
334     args = parseargs(args)
335
336     for arg in args:
337
338         # when 'update' is run inside a project dir, it should...
339         if is_project_dir(arg):
340
341             prj = Project(arg)
342
343             # (a) update all packages
344             args += prj.pacs_have
345
346             # (b) fetch new packages
347             prj.checkout_missing_pacs()
348             args.remove(arg)
349
350
351     pacs = findpacs(args)
352
353     for p in pacs:
354
355         # save filelist and (modified) status before replacing the meta file
356         saved_filenames = p.filenamelist
357         saved_modifiedfiles = [ f for f in p.filenamelist if p.status(f) == 'M' ]
358
359         oldp = p
360         p.update_filesmeta()
361         p = Package(p.dir)
362
363         # which files do no longer exist upstream?
364         disappeared = [ f for f in saved_filenames if f not in p.filenamelist ]
365             
366
367         for filename in saved_filenames:
368             if filename in disappeared:
369                 print statfrmt('D', filename)
370                 p.delete_localfile(filename)
371                 continue
372
373         for filename in p.filenamelist:
374
375             state = p.status(filename)
376             if state == 'M' and p.findfilebyname(filename).md5 == oldp.findfilebyname(filename).md5:
377                 # no merge necessary... local file is changed, but upstream isn't
378                 pass
379             elif state == 'M' and filename in saved_modifiedfiles:
380                 status_after_merge = p.mergefile(filename)
381                 print statfrmt(status_after_merge, filename)
382             elif state == 'M':
383                 p.updatefile(filename)
384                 print statfrmt('U', filename)
385             elif state == '!':
386                 p.updatefile(filename)
387                 print 'Restored \'%s\'' % filename
388             elif state == 'F':
389                 p.updatefile(filename)
390                 print statfrmt('A', filename)
391             elif state == ' ':
392                 pass
393
394
395         p.update_pacmeta()
396
397         #print ljust(p.name, 45), 'At revision %s.' % p.rev
398         print 'At revision %s.' % p.rev
399                 
400
401
402         
403 def delete(args):
404     """rm (remove, del, delete): Mark files to be deleted upon next 'checkin'
405
406 usage: osc rm file1 file2 ...
407     """
408
409     if not args:
410         print 'delete requires at least one argument'
411         sys.exit(1)
412
413     args = parseargs(args)
414     pacs = findpacs(args)
415
416     for p in pacs:
417
418         for filename in p.todo:
419             p.put_on_deletelist(filename)
420             p.write_deletelist()
421             try:
422                 os.unlink(os.path.join(p.dir, filename))
423                 os.unlink(os.path.join(p.storedir, filename))
424             except:
425                 pass
426             print statfrmt('D', filename)
427
428
429 def resolved(args):
430     """If an update can't be merged automatically, a file is in 'C' (conflict)
431 state, and conflicts are marked with special <<<<<<< and >>>>>>> lines. 
432 After manually resolving the problem, use
433
434 usage: osc resolved <filename>
435 """
436
437     if not args:
438         print 'this command requires at least one argument'
439         sys.exit(1)
440
441     args = parseargs(args)
442     pacs = findpacs(args)
443
444     for p in pacs:
445
446         for filename in p.todo:
447             print "Resolved conflicted state of '%s'" % filename
448             p.clear_from_conflictlist(filename)
449
450
451 def userid(args):
452     """id:  show metadata about user <userid>
453
454 usage: osc id <userid>
455     """
456
457     if not args:
458         print 'this command requires at least one argument'
459         sys.exit(1)
460
461     r = get_user_id(args[0])
462     if r:
463         print ''.join(r)
464
465
466 def platforms(args):
467     """platforms: Shows platforms
468
469 usage 1. osc platforms
470             Shows available platforms/build targets
471
472       2. osc platforms <project>
473             Shows the configured platforms/build targets of a project
474     """
475
476     if args:
477         project = args[0]
478         print '\n'.join(get_platforms_of_project(project))
479     else:
480         print '\n'.join(get_platforms())
481
482
483 def results_meta(args):
484     """Shows the build results of the package in raw XML
485
486 usage: osc results_meta [platform]
487     """
488     wd = os.curdir
489     package = store_read_package(wd)
490     project = store_read_project(wd)
491     if args:
492         platform = args[0]
493         print ''.join(show_results_meta(project, package, platform))
494     else:
495         for platform in get_platforms_of_project(project):
496             print ''.join(show_results_meta(project, package, platform))
497
498             
499 def results(args):
500     """Shows the build results of a package
501
502 usage: 1. osc results                   # package = current dir
503        2. osc results <packagedir>
504     """
505
506     if args and len(args) > 1:
507         print 'getting results for more than one package is not supported'
508         print sys.exit(1)
509         
510     if args:
511         wd = args[0]
512     else:
513         wd = os.curdir
514     package = store_read_package(wd)
515     project = store_read_project(wd)
516
517     for platform in get_platforms_of_project(project):
518         print '\n'.join(get_results(project, package, platform))
519
520             
521 def log(args):
522     """log: Shows the log file from a package (you need to be inside a package directory)
523
524 usage: osc log <platform> <arch>
525
526 To find out <platform> and <arch>, you can use 'osc results'
527
528     """
529     wd = os.curdir
530     package = store_read_package(wd)
531     project = store_read_project(wd)
532
533     platform = args[0]
534     arch = args[1]
535     offset = 0
536     try:
537         while True:
538             log_chunk = get_log(project, package, platform, arch, offset)
539             if len(log_chunk) == 0:
540                 break
541             offset += len(log_chunk)
542             print log_chunk.strip()
543     except KeyboardInterrupt:
544         pass
545
546
547 def buildinfo(args):
548     """buildinfo: Shows the build "info" which is used in building a package 
549 You need to call the command inside a package directory.
550
551 usage: osc buildinfo <platform> <arch>
552     """
553     wd = os.curdir
554     package = store_read_package(wd)
555     project = store_read_project(wd)
556
557     if args is None or len(args) < 2:
558         print 'missing argument'
559         print buildinfo.func_doc
560         print 'Valid arguments for this package are:'
561         print 
562         repos(None)
563         print
564         sys.exit(1)
565         
566     platform = args[0]
567     arch = args[1]
568     print ''.join(get_buildinfo(project, package, platform, arch))
569
570
571 def buildconfig(args):
572     """buildconfig: Shows the build configuration which is used in building a package
573 You need to call the command inside a package directory.
574
575 usage: osc buildconfig <platform> <arch>
576     """
577     wd = os.curdir
578     package = store_read_package(wd)
579     project = store_read_project(wd)
580
581     if args is None or len(args) < 2:
582         print 'missing argument'
583         print buildconfig.func_doc
584         print 'Valid arguments for this package are:'
585         print 
586         repos(None)
587         print
588         sys.exit(1)
589         
590     platform = args[0]
591     arch = args[1]
592     print ''.join(get_buildconfig(project, package, platform, arch))
593
594
595 def repos(args):
596     """repos: Shows the repositories which are defined for a package
597
598 usage: 1. osc repos                   # package = current dir
599        2. osc repos <packagedir>
600     """
601     args = parseargs(args)
602     pacs = findpacs(args)
603
604     for p in pacs:
605
606         for platform in get_repos_of_project(p.prjname):
607             print platform
608
609
610 def build(args):
611     """build: build a package _locally_
612 You need to call the command inside a package directory.
613
614 usage: osc build <platform> <arch> <specfile>
615     """
616
617     if args is None or len(args) < 3:
618         print 'missing argument'
619         print build.func_doc
620         print 'Valid arguments are:'
621         print 
622         repos(None)
623         print
624         sys.exit(1)
625
626     import osc.build
627     osc.build.main(sys.argv[1:])
628
629         
630
631
632 def history(args):
633     """history: Shows the build history of a package (NOT IMPLEMENTED YET)
634
635 usage: osc history <pacdir>
636     """
637     args = parseargs(args)
638     pacs = findpacs(args)
639
640     for p in pacs:
641         print ''.join(get_history(p.prjname, p.name))
642
643
644 def rebuildpac(args):
645     """rebuildpac: Triggers a package rebuild for all repositories/architectures of the package
646
647 usage: osc rebuildpac <pacdir>
648     """ 
649     args = parseargs(args)
650     pacs = findpacs(args)
651
652     for p in pacs:
653         print p.name + ':', cmd_rebuild(p.prjname, p.name)
654
655
656 def help(args):
657     """help: Describe the usage of this program or its subcommands.
658
659 usage: osc help [SUBCOMMAND...]
660     """
661     if args:
662         cmd = args[0]
663         for i in cmd_dict.keys():
664             if cmd in cmd_dict[i]:
665                 cmd = i
666                 break
667
668         try:
669             print cmd.func_doc
670
671         except AttributeError, KeyError:
672             print 'unknown command \'%s\'' % cmd
673             sys.exit(1)
674     else:
675         print usage_general
676         lines = []
677         for i in cmd_dict.keys():
678             line = '    ' + (i.__name__)
679             if len(cmd_dict[i]) > 1:
680                 line += ' (%s)' % ', '.join(cmd_dict[i][1:])
681             lines.append(line)
682         lines.sort()
683         lines.append('')
684         print '\n'.join(lines)
685
686
687 # all commands and aliases are defined here
688 # a function with the respective name is assumed to exist
689 cmd_dict = {
690     add:            ['add'],
691     addremove:      ['addremove'],
692     build:          ['build'],
693     buildconfig:    ['buildconfig'],
694     buildinfo:      ['buildinfo'],
695     commit:         ['commit', 'ci', 'checkin'],
696     checkout:       ['checkout', 'co'],
697     updatepacmetafromspec:       ['updatepacmetafromspec'],
698     diff:           ['diff'],
699     editmeta:       ['editmeta'],
700     help:           ['help'],
701     history:        ['history', 'hist'],
702     userid:         ['id'],         # <- small difference here
703     init:           ['init'],           # depracated
704     log:            ['log'],
705     ls:             ['ls', 'list'],
706     meta:           ['meta'],
707     platforms:      ['platforms'],
708     delete:         ['delete', 'del', 'rm', 'remove'],
709     repos:          ['repos'],
710     repourls:       ['repourls'],
711     resolved:       ['resolved'],
712     results:        ['results'],
713     results_meta:   ['results_meta'],
714     rebuildpac:     ['rebuildpac'],
715     status:         ['status', 'st'],
716     update:         ['update', 'up'],
717 }
718
719
720 def main():
721     """handling of commandline arguments, and dispatching to subcommands"""
722
723     # which subcommand?
724     if len(sys.argv) < 2:
725         print "Type 'osc help' for usage."
726         sys.exit(0)
727
728     cmd = sys.argv[1]
729
730     # more arguments?
731     if len(sys.argv) > 2:
732         args = sys.argv[2:]
733     else:
734         args = None
735
736     for i in cmd_dict.keys():
737         if cmd in cmd_dict[i]:
738             cmd = i
739         
740     # run subcommand
741     if cmd not in cmd_dict:
742         print 'unknown command \'%s\'' % cmd
743         print "Type 'osc help' for usage."
744         sys.exit(1)
745     cmd(args)
746
747
748 if __name__ == '__main__':
749     init_basicauth()
750     main()
751