14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""Bazaar-NG -- a free distributed version-control tool
20
**WARNING: THIS IS AN UNSTABLE DEVELOPMENT VERSION**
22
* Metadata format is not stable yet -- you may need to
23
discard history in the future.
25
* Many commands unimplemented or partially implemented.
27
* Space-inefficient storage.
29
* No merge operators yet.
36
Show software version/licence/non-warranty.
38
Start versioning the current directory
42
Show revision history.
45
bzr move FROM... DESTDIR
46
Move one or more files to a different directory.
48
Show changes from last revision to working copy.
49
bzr commit -m 'MESSAGE'
50
Store current state as new revision.
51
bzr export [-r REVNO] DESTINATION
52
Export the branch state at a previous version.
54
Show summary of pending changes.
56
Make a file not versioned.
58
Show statistics about this branch.
60
Verify history is stored safely.
61
(for more type 'bzr help commands')
67
import sys, os, time, types, shutil, tempfile, fnmatch, difflib, os.path
69
from pprint import pprint
74
from bzrlib.store import ImmutableStore
22
75
from bzrlib.trace import mutter, note, log_error
23
76
from bzrlib.errors import bailout, BzrError, BzrCheckError, BzrCommandError
24
from bzrlib.osutils import quotefn
25
from bzrlib import Branch, Inventory, InventoryEntry, BZRDIR, \
77
from bzrlib.osutils import quotefn, pumpfile, isdir, isfile
78
from bzrlib.tree import RevisionTree, EmptyTree, WorkingTree, Tree
79
from bzrlib.revision import Revision
80
from bzrlib import Branch, Inventory, InventoryEntry, ScratchBranch, BZRDIR, \
29
def _squish_command_name(cmd):
30
return 'cmd_' + cmd.replace('-', '_')
33
def _unsquish_command_name(cmd):
34
assert cmd.startswith("cmd_")
35
return cmd[4:].replace('_','-')
37
def _parse_revision_str(revstr):
38
"""This handles a revision string -> revno.
40
There are several possibilities:
43
'234:345' -> [234, 345]
47
In the future we will also support:
48
'uuid:blah-blah-blah' -> ?
49
'hash:blahblahblah' -> ?
53
if revstr.find(':') != -1:
54
revs = revstr.split(':')
56
raise ValueError('More than 2 pieces not supported for --revision: %r' % revstr)
61
revs[0] = int(revs[0])
66
revs[1] = int(revs[1])
72
"""Return canonical name and class for all registered commands."""
73
for k, v in globals().iteritems():
74
if k.startswith("cmd_"):
75
yield _unsquish_command_name(k), v
83
BZR_DIFF_FORMAT = "## Bazaar-NG diff, format 0 ##\n"
84
BZR_PATCHNAME_FORMAT = 'cset:sha1:%s'
86
## standard representation
87
NONE_STRING = '(none)'
77
101
def get_cmd_class(cmd):
78
"""Return the canonical name and command class for a command.
80
cmd = str(cmd) # not unicode
82
# first look up this command under the specified name
104
cmd = CMD_ALIASES.get(cmd, cmd)
84
return cmd, globals()[_squish_command_name(cmd)]
107
cmd_class = globals()['cmd_' + cmd.replace('-', '_')]
88
# look for any command which claims this as an alias
89
for cmdname, cmdclass in get_all_cmds():
90
if cmd in cmdclass.aliases:
91
return cmdname, cmdclass
93
cmdclass = ExternalCommand.find_command(cmd)
97
raise BzrCommandError("unknown command %r" % cmd)
100
class Command(object):
109
raise BzrError("unknown command %r" % cmd)
111
return cmd, cmd_class
101
116
"""Base class for commands.
103
118
The docstring for an actual command should give a single-line
140
155
This is invoked with the options and arguments bound to
141
156
keyword parameters.
143
Return 0 or None if the command was successful, or a shell
158
Return True if the command was successful, False if not.
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
164
class cmd_status(Command):
222
165
"""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.
167
For each file there is a single line giving its file state and name.
168
The name is that in the current revision unless it is deleted or
169
missing, in which case the old name is shown.
257
takes_args = ['file*']
258
takes_options = ['all', 'show-ids']
259
aliases = ['st', 'stat']
171
takes_options = ['all']
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)
173
def run(self, all=False):
174
#import bzrlib.status
175
#bzrlib.status.tree_status(Branch('.'))
176
Branch('.').show_status(show_all=all)
276
179
class cmd_cat_revision(Command):
313
216
recursively add that parent, rather than giving an error?
315
218
takes_args = ['file+']
316
takes_options = ['verbose', 'no-recurse']
219
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)
322
class cmd_relpath(Command):
221
def run(self, file_list, verbose=False):
222
bzrlib.add.smart_add(file_list, verbose)
225
def Relpath(Command):
323
226
"""Show path of a file relative to root"""
324
takes_args = ['filename']
227
takes_args = ('filename')
327
def run(self, filename):
328
print Branch(filename).relpath(filename)
230
print Branch(self.args['filename']).relpath(filename)
332
234
class cmd_inventory(Command):
333
235
"""Show inventory of the current working copy or a revision."""
334
takes_options = ['revision', 'show-ids']
236
takes_options = ['revision']
336
def run(self, revision=None, show_ids=False):
238
def run(self, revision=None):
338
240
if revision == None:
339
241
inv = b.read_working_inventory()
341
243
inv = b.get_revision_inventory(b.lookup_revision(revision))
343
for path, entry in inv.entries():
345
print '%-50s %s' % (path, entry.file_id)
245
for path, entry in inv.iter_entries():
246
print '%-50s %s' % (entry.file_id, path)
350
249
class cmd_move(Command):
527
420
takes_args = ['file*']
528
takes_options = ['revision', 'diff-options']
421
takes_options = ['revision']
531
def run(self, revision=None, file_list=None, diff_options=None):
423
def run(self, revision=None, file_list=None):
532
424
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)
426
show_diff(Branch('.'), revision, file_list)
551
429
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
451
class cmd_root(Command):
620
452
"""Show the tree root directory.
624
456
takes_args = ['filename?']
625
457
def run(self, filename=None):
626
458
"""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')
459
print bzrlib.branch.find_branch_root(filename)
632
463
class cmd_log(Command):
633
464
"""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
639
TODO: Make --revision support uuid: and hash: [future tag:] notation.
466
TODO: Options to show ids; to limit range; etc.
643
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'
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)
679
# use 'replace' so that we don't abort if trying to write out
680
# in e.g. the default C locale.
681
outf = codecs.getwriter(bzrlib.user_encoding)(sys.stdout, errors='replace')
684
show_timezone=timezone,
689
start_revision=revision[0],
690
end_revision=revision[1])
694
class cmd_touching_revisions(Command):
695
"""Return revision-ids which affected a particular file.
697
A more user-friendly interface is "bzr log FILE"."""
699
takes_args = ["filename"]
700
def run(self, filename):
702
inv = b.read_working_inventory()
703
file_id = inv.path2id(b.relpath(filename))
704
for revno, revision_id, what in bzrlib.log.find_touching_revisions(b, file_id):
705
print "%6d %s" % (revno, what)
468
takes_options = ['timezone', 'verbose']
469
def run(self, timezone='original', verbose=False):
470
Branch('.').write_log(show_timezone=timezone, verbose=verbose)
708
473
class cmd_ls(Command):
744
509
class cmd_ignore(Command):
745
"""Ignore a command or pattern
747
To remove patterns from the ignore list, edit the .bzrignore file.
749
If the pattern contains a slash, it is compared to the whole path
750
from the branch root. Otherwise, it is comapred to only the last
751
component of the path.
753
Ignore patterns are case-insensitive on case-insensitive systems.
755
Note: wildcards must be quoted from the shell on Unix.
758
bzr ignore ./Makefile
510
"""Ignore a command or pattern"""
761
511
takes_args = ['name_pattern']
763
513
def run(self, name_pattern):
764
from bzrlib.atomicfile import AtomicFile
768
ifn = b.abspath('.bzrignore')
770
if os.path.exists(ifn):
773
igns = f.read().decode('utf-8')
779
# TODO: If the file already uses crlf-style termination, maybe
780
# we should use that for the newly added lines?
782
if igns and igns[-1] != '\n':
784
igns += name_pattern + '\n'
787
f = AtomicFile(ifn, 'wt')
788
f.write(igns.encode('utf-8'))
516
# XXX: This will fail if it's a hardlink; should use an AtomicFile class.
517
f = open(b.abspath('.bzrignore'), 'at')
518
f.write(name_pattern + '\n')
793
521
inv = b.working_tree().inventory
794
522
if inv.path2id('.bzrignore'):
871
596
class cmd_commit(Command):
872
597
"""Commit changes into a new revision.
874
If selected files are specified, only changes to those files are
875
committed. If a directory is specified then its contents are also
878
A selected-file commit may fail in some cases where the committed
879
tree would be invalid, such as trying to commit a file in a
880
newly-added directory that is not itself committed.
599
TODO: Commit only selected files.
882
601
TODO: Run hooks on tree to-be-committed, and after commit.
884
603
TODO: Strict commit that fails if there are unknown or deleted files.
886
takes_args = ['selected*']
887
takes_options = ['message', 'file', 'verbose']
888
aliases = ['ci', 'checkin']
890
def run(self, message=None, file=None, verbose=True, selected_list=None):
891
from bzrlib.commit import commit
893
## Warning: shadows builtin file()
894
if not message and not file:
895
raise BzrCommandError("please specify a commit message",
896
["use either --message or --file"])
897
elif message and file:
898
raise BzrCommandError("please specify either --message or --file")
902
message = codecs.open(file, 'rt', bzrlib.user_encoding).read()
905
commit(b, message, verbose=verbose, specific_files=selected_list)
605
takes_options = ['message', 'verbose']
607
def run(self, message=None, verbose=False):
609
raise BzrCommandError("please specify a commit message")
610
Branch('.').commit(message, verbose=verbose)
908
613
class cmd_check(Command):
933
638
"""Run internal test suite"""
936
from bzrlib.selftest import selftest
641
failures, tests = 0, 0
643
import doctest, bzrlib.store, bzrlib.tests
644
bzrlib.trace.verbose = False
646
for m in bzrlib.store, bzrlib.inventory, bzrlib.branch, bzrlib.osutils, \
647
bzrlib.tree, bzrlib.tests, bzrlib.commands, bzrlib.add:
648
mf, mt = doctest.testmod(m)
651
print '%-40s %3d tests' % (m.__name__, mt),
653
print '%3d FAILED!' % mf
657
print '%-40s %3d tests' % ('total', tests),
659
print '%3d FAILED!' % failures
967
684
print "it sure does!"
969
def parse_spec(spec):
975
>>> parse_spec("../@")
977
>>> parse_spec("../f/@35")
983
parsed = spec.split('/@')
984
assert len(parsed) == 2
988
parsed[1] = int(parsed[1])
991
parsed = [spec, None]
994
class cmd_merge(Command):
995
"""Perform a three-way merge of trees.
997
The SPEC parameters are working tree or revision specifiers. Working trees
998
are specified using standard paths or urls. No component of a directory
999
path may begin with '@'.
1001
Working tree examples: '.', '..', 'foo@', but NOT 'foo/@bar'
1003
Revisions are specified using a dirname/@revno pair, where dirname is the
1004
branch directory and revno is the revision within that branch. If no revno
1005
is specified, the latest revision is used.
1007
Revision examples: './@127', 'foo/@', '../@1'
1009
The OTHER_SPEC parameter is required. If the BASE_SPEC parameter is
1010
not supplied, the common ancestor of OTHER_SPEC the current branch is used
1013
takes_args = ['other_spec', 'base_spec?']
1015
def run(self, other_spec, base_spec=None):
1016
from bzrlib.merge import merge
1017
merge(parse_spec(other_spec), parse_spec(base_spec))
1020
class cmd_revert(Command):
1022
Reverse all changes since the last commit. Only versioned files are
1025
takes_options = ['revision']
1027
def run(self, revision=-1):
1028
merge.merge(('.', revision), parse_spec('.'), no_changes=False,
1032
687
class cmd_assert_fail(Command):
1033
688
"""Test reporting of assertion failures"""
1042
697
For a list of all available commands, say 'bzr help commands'."""
1043
698
takes_args = ['topic?']
1046
700
def run(self, topic=None):
1051
class cmd_update_stat_cache(Command):
1052
"""Update stat-cache mapping inodes to SHA-1 hashes.
1054
For testing only."""
1059
statcache.update_cache(b.base, b.read_working_inventory())
704
def help(topic=None):
707
elif topic == 'commands':
710
help_on_command(topic)
713
def help_on_command(cmdname):
714
cmdname = str(cmdname)
716
from inspect import getdoc
717
topic, cmdclass = get_cmd_class(cmdname)
719
doc = getdoc(cmdclass)
721
raise NotImplementedError("sorry, no detailed help yet for %r" % cmdname)
724
short, rest = doc.split('\n', 1)
729
print 'usage: bzr ' + topic,
730
for aname in cmdclass.takes_args:
731
aname = aname.upper()
732
if aname[-1] in ['$', '+']:
733
aname = aname[:-1] + '...'
734
elif aname[-1] == '?':
735
aname = '[' + aname[:-1] + ']'
736
elif aname[-1] == '*':
737
aname = '[' + aname[:-1] + '...]'
744
if cmdclass.takes_options:
747
for on in cmdclass.takes_options:
752
"""List all commands"""
756
for k, v in globals().items():
757
if k.startswith('cmd_'):
758
accu.append((k[4:].replace('_','-'), v))
760
for cmdname, cmdclass in accu:
764
help = inspect.getdoc(cmdclass)
766
print " " + help.split('\n', 1)[0]
769
######################################################################
1063
773
# list of all available options; the rhs can be either None for an
1312
1015
msg = 'assertion failed'
1314
1017
msg += ': ' + str(e)
1315
_report_exception(msg)
1018
_report_exception(e, msg)
1317
1020
except KeyboardInterrupt, e:
1318
_report_exception('interrupted', quiet=True)
1021
_report_exception(e, 'interrupted', quiet=True)
1320
1023
except Exception, e:
1322
if (isinstance(e, IOError)
1323
and hasattr(e, 'errno')
1324
and e.errno == errno.EPIPE):
1025
if isinstance(e, IOError) and e.errno == errno.EPIPE:
1326
1027
msg = 'broken pipe'
1328
1029
msg = str(e).rstrip('\n')
1329
_report_exception(msg, quiet)
1030
_report_exception(e, msg, quiet)
1332
1033
bzrlib.trace.close_trace()