19
import sys, os, time, os.path
22
23
from bzrlib.trace import mutter, note, log_error
23
24
from bzrlib.errors import bailout, BzrError, BzrCheckError, BzrCommandError
24
from bzrlib.osutils import quotefn
25
from bzrlib import Branch, Inventory, InventoryEntry, BZRDIR, \
25
from bzrlib.osutils import quotefn, pumpfile, isdir, isfile
26
from bzrlib.tree import RevisionTree, EmptyTree, WorkingTree, Tree
27
from bzrlib.revision import Revision
28
from bzrlib import Branch, Inventory, InventoryEntry, ScratchBranch, BZRDIR, \
149
class ExternalCommand(Command):
150
"""Class to wrap external commands.
152
We cheat a little here, when get_cmd_class() calls us we actually give it back
153
an object we construct that has the appropriate path, help, options etc for the
156
When run_bzr() tries to instantiate that 'class' it gets caught by the __call__
157
method, which we override to call the Command.__init__ method. That then calls
158
our run method which is pretty straight forward.
160
The only wrinkle is that we have to map bzr's dictionary of options and arguments
161
back into command line options and arguments for the script.
164
def find_command(cls, cmd):
166
bzrpath = os.environ.get('BZRPATH', '')
168
for dir in bzrpath.split(':'):
169
path = os.path.join(dir, cmd)
170
if os.path.isfile(path):
171
return ExternalCommand(path)
175
find_command = classmethod(find_command)
177
def __init__(self, path):
180
# TODO: If either of these fail, we should detect that and
181
# assume that path is not really a bzr plugin after all.
183
pipe = os.popen('%s --bzr-usage' % path, 'r')
184
self.takes_options = pipe.readline().split()
185
self.takes_args = pipe.readline().split()
188
pipe = os.popen('%s --bzr-help' % path, 'r')
189
self.__doc__ = pipe.read()
192
def __call__(self, options, arguments):
193
Command.__init__(self, options, arguments)
196
def run(self, **kargs):
204
if OPTIONS.has_key(name):
206
opts.append('--%s' % name)
207
if value is not None and value is not True:
208
opts.append(str(value))
210
# it's an arg, or arg list
211
if type(value) is not list:
217
self.status = os.spawnv(os.P_WAIT, self.path, [self.path] + opts + args)
221
115
class cmd_status(Command):
222
116
"""Display status summary.
224
This reports on versioned and unknown files, reporting them
225
grouped by state. Possible states are:
228
Versioned in the working copy but not in the previous revision.
231
Versioned in the previous revision but removed or deleted
235
Path of this file changed from the previous revision;
236
the text may also have changed. This includes files whose
237
parent directory was renamed.
240
Text has changed since the previous revision.
243
Nothing about this file has changed since the previous revision.
244
Only shown with --all.
247
Not versioned and not matching an ignore pattern.
249
To see ignored files use 'bzr ignored'. For details in the
250
changes to file texts, use 'bzr diff'.
252
If no arguments are specified, the status of the entire working
253
directory is shown. Otherwise, only the status of the specified
254
files or directories is reported. If a directory is given, status
255
is reported for everything inside that directory.
118
For each file there is a single line giving its file state and name.
119
The name is that in the current revision unless it is deleted or
120
missing, in which case the old name is shown.
257
takes_args = ['file*']
258
takes_options = ['all', 'show-ids']
122
takes_options = ['all']
259
123
aliases = ['st', 'stat']
261
def run(self, all=False, show_ids=False, file_list=None):
263
b = Branch(file_list[0])
264
file_list = [b.relpath(x) for x in file_list]
265
# special case: only one path was given and it's the root
267
if file_list == ['']:
272
status.show_status(b, show_unchanged=all, show_ids=show_ids,
273
specific_files=file_list)
125
def run(self, all=False):
126
#import bzrlib.status
127
#bzrlib.status.tree_status(Branch('.'))
128
Branch('.').show_status(show_all=all)
276
131
class cmd_cat_revision(Command):
313
168
recursively add that parent, rather than giving an error?
315
170
takes_args = ['file+']
316
takes_options = ['verbose', 'no-recurse']
171
takes_options = ['verbose']
318
def run(self, file_list, verbose=False, no_recurse=False):
319
bzrlib.add.smart_add(file_list, verbose, not no_recurse)
173
def run(self, file_list, verbose=False):
174
bzrlib.add.smart_add(file_list, verbose)
322
177
class cmd_relpath(Command):
323
178
"""Show path of a file relative to root"""
324
179
takes_args = ['filename']
327
181
def run(self, filename):
328
182
print Branch(filename).relpath(filename)
332
186
class cmd_inventory(Command):
333
187
"""Show inventory of the current working copy or a revision."""
334
takes_options = ['revision', 'show-ids']
188
takes_options = ['revision']
336
def run(self, revision=None, show_ids=False):
190
def run(self, revision=None):
338
192
if revision == None:
339
193
inv = b.read_working_inventory()
341
195
inv = b.get_revision_inventory(b.lookup_revision(revision))
343
for path, entry in inv.entries():
345
print '%-50s %s' % (path, entry.file_id)
197
for path, entry in inv.iter_entries():
198
print '%-50s %s' % (entry.file_id, path)
350
201
class cmd_move(Command):
527
372
takes_args = ['file*']
528
takes_options = ['revision', 'diff-options']
373
takes_options = ['revision']
531
def run(self, revision=None, file_list=None, diff_options=None):
376
def run(self, revision=None, file_list=None):
532
377
from bzrlib.diff import show_diff
533
from bzrlib import find_branch
536
b = find_branch(file_list[0])
537
file_list = [b.relpath(f) for f in file_list]
538
if file_list == ['']:
539
# just pointing to top-of-tree
544
show_diff(b, revision, specific_files=file_list,
545
external_diff_options=diff_options)
379
show_diff(Branch('.'), revision, file_list)
551
382
class cmd_deleted(Command):
574
class cmd_modified(Command):
575
"""List files modified in working tree."""
580
inv = b.read_working_inventory()
581
sc = statcache.update_cache(b, inv)
582
basis = b.basis_tree()
583
basis_inv = basis.inventory
585
# We used to do this through iter_entries(), but that's slow
586
# when most of the files are unmodified, as is usually the
587
# case. So instead we iterate by inventory entry, and only
588
# calculate paths as necessary.
590
for file_id in basis_inv:
591
cacheentry = sc.get(file_id)
592
if not cacheentry: # deleted
594
ie = basis_inv[file_id]
595
if cacheentry[statcache.SC_SHA1] != ie.text_sha1:
596
path = inv.id2path(file_id)
601
class cmd_added(Command):
602
"""List files added in working tree."""
606
wt = b.working_tree()
607
basis_inv = b.basis_tree().inventory
610
if file_id in basis_inv:
612
path = inv.id2path(file_id)
613
if not os.access(b.abspath(path), os.F_OK):
619
404
class cmd_root(Command):
620
405
"""Show the tree root directory.
624
409
takes_args = ['filename?']
625
410
def run(self, filename=None):
626
411
"""Print the branch root."""
627
from branch import find_branch
628
b = find_branch(filename)
629
print getattr(b, 'base', None) or getattr(b, 'baseurl')
412
print bzrlib.branch.find_branch_root(filename)
632
416
class cmd_log(Command):
633
417
"""Show log of this branch.
635
To request a range of logs, you can use the command -r begin:end
636
-r revision requests a specific revision, -r :end or -r begin: are
419
TODO: Option to limit range.
639
TODO: Make --revision support uuid: and hash: [future tag:] notation.
421
TODO: Perhaps show most-recent first with an option for last.
643
423
takes_args = ['filename?']
644
takes_options = ['forward', 'timezone', 'verbose', 'show-ids', 'revision']
646
def run(self, filename=None, timezone='original',
651
from bzrlib import show_log, find_branch
654
direction = (forward and 'forward') or 'reverse'
424
takes_options = ['timezone', 'verbose', 'show-ids']
425
def run(self, filename=None, timezone='original', verbose=False, show_ids=False):
426
b = Branch((filename or '.'), lock_mode='r')
657
b = find_branch(filename)
658
fp = b.relpath(filename)
660
file_id = b.read_working_inventory().path2id(fp)
662
file_id = None # points to branch root
668
revision = [None, None]
669
elif isinstance(revision, int):
670
revision = [revision, revision]
675
assert len(revision) == 2
677
mutter('encoding log as %r' % bzrlib.user_encoding)
678
outf = codecs.getwriter(bzrlib.user_encoding)(sys.stdout)
681
show_timezone=timezone,
686
start_revision=revision[0],
687
end_revision=revision[1])
428
filename = b.relpath(filename)
429
bzrlib.show_log(b, filename,
430
show_timezone=timezone,
691
436
class cmd_touching_revisions(Command):
692
"""Return revision-ids which affected a particular file.
694
A more user-friendly interface is "bzr log FILE"."""
437
"""Return revision-ids which affected a particular file."""
696
439
takes_args = ["filename"]
697
440
def run(self, filename):
441
b = Branch(filename, lock_mode='r')
699
442
inv = b.read_working_inventory()
700
443
file_id = inv.path2id(b.relpath(filename))
701
444
for revno, revision_id, what in bzrlib.log.find_touching_revisions(b, file_id):
741
484
class cmd_ignore(Command):
742
"""Ignore a command or pattern
744
To remove patterns from the ignore list, edit the .bzrignore file.
746
If the pattern contains a slash, it is compared to the whole path
747
from the branch root. Otherwise, it is comapred to only the last
748
component of the path.
750
Ignore patterns are case-insensitive on case-insensitive systems.
752
Note: wildcards must be quoted from the shell on Unix.
755
bzr ignore ./Makefile
485
"""Ignore a command or pattern"""
758
486
takes_args = ['name_pattern']
760
488
def run(self, name_pattern):
761
from bzrlib.atomicfile import AtomicFile
765
ifn = b.abspath('.bzrignore')
767
if os.path.exists(ifn):
770
igns = f.read().decode('utf-8')
776
# TODO: If the file already uses crlf-style termination, maybe
777
# we should use that for the newly added lines?
779
if igns and igns[-1] != '\n':
781
igns += name_pattern + '\n'
784
f = AtomicFile(ifn, 'wt')
785
f.write(igns.encode('utf-8'))
491
# XXX: This will fail if it's a hardlink; should use an AtomicFile class.
492
f = open(b.abspath('.bzrignore'), 'at')
493
f.write(name_pattern + '\n')
790
496
inv = b.working_tree().inventory
791
497
if inv.path2id('.bzrignore'):
868
572
class cmd_commit(Command):
869
573
"""Commit changes into a new revision.
871
If selected files are specified, only changes to those files are
872
committed. If a directory is specified then its contents are also
875
A selected-file commit may fail in some cases where the committed
876
tree would be invalid, such as trying to commit a file in a
877
newly-added directory that is not itself committed.
575
TODO: Commit only selected files.
879
577
TODO: Run hooks on tree to-be-committed, and after commit.
881
579
TODO: Strict commit that fails if there are unknown or deleted files.
883
takes_args = ['selected*']
884
581
takes_options = ['message', 'file', 'verbose']
885
582
aliases = ['ci', 'checkin']
887
def run(self, message=None, file=None, verbose=True, selected_list=None):
888
from bzrlib.commit import commit
584
def run(self, message=None, file=None, verbose=False):
890
585
## Warning: shadows builtin file()
891
586
if not message and not file:
892
587
raise BzrCommandError("please specify a commit message",
933
627
failures, tests = 0, 0
935
import doctest, bzrlib.store
629
import doctest, bzrlib.store, bzrlib.tests
936
630
bzrlib.trace.verbose = False
938
632
for m in bzrlib.store, bzrlib.inventory, bzrlib.branch, bzrlib.osutils, \
939
bzrlib.tree, bzrlib.commands, bzrlib.add:
633
bzrlib.tree, bzrlib.tests, bzrlib.commands, bzrlib.add:
940
634
mf, mt = doctest.testmod(m)