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
19
import sys, os, time, os.path
74
from bzrlib.store import ImmutableStore
75
22
from bzrlib.trace import mutter, note, log_error
76
23
from bzrlib.errors import bailout, BzrError, BzrCheckError, BzrCommandError
77
24
from bzrlib.osutils import quotefn, pumpfile, isdir, isfile
78
from bzrlib.tree import RevisionTree, EmptyTree, WorkingTree, Tree
25
from bzrlib.tree import RevisionTree, EmptyTree, Tree
79
26
from bzrlib.revision import Revision
80
27
from bzrlib import Branch, Inventory, InventoryEntry, ScratchBranch, BZRDIR, \
83
BZR_DIFF_FORMAT = "## Bazaar-NG diff, format 0 ##\n"
84
BZR_PATCHNAME_FORMAT = 'cset:sha1:%s'
86
## standard representation
87
NONE_STRING = '(none)'
29
from bzrlib import merge
32
def _squish_command_name(cmd):
33
return 'cmd_' + cmd.replace('-', '_')
36
def _unsquish_command_name(cmd):
37
assert cmd.startswith("cmd_")
38
return cmd[4:].replace('_','-')
41
"""Return canonical name and class for all registered commands."""
42
for k, v in globals().iteritems():
43
if k.startswith("cmd_"):
44
yield _unsquish_command_name(k), v
101
46
def get_cmd_class(cmd):
104
cmd = CMD_ALIASES.get(cmd, cmd)
47
"""Return the canonical name and command class for a command.
49
cmd = str(cmd) # not unicode
51
# first look up this command under the specified name
107
cmd_class = globals()['cmd_' + cmd.replace('-', '_')]
53
return cmd, globals()[_squish_command_name(cmd)]
109
raise BzrError("unknown command %r" % cmd)
111
return cmd, cmd_class
57
# look for any command which claims this as an alias
58
for cmdname, cmdclass in get_all_cmds():
59
if cmd in cmdclass.aliases:
60
return cmdname, cmdclass
62
cmdclass = ExternalCommand.find_command(cmd)
66
raise BzrCommandError("unknown command %r" % cmd)
155
109
This is invoked with the options and arguments bound to
156
110
keyword parameters.
158
Return True if the command was successful, False if not.
112
Return 0 or None if the command was successful, or a shell
118
class ExternalCommand(Command):
119
"""Class to wrap external commands.
121
We cheat a little here, when get_cmd_class() calls us we actually give it back
122
an object we construct that has the appropriate path, help, options etc for the
125
When run_bzr() tries to instantiate that 'class' it gets caught by the __call__
126
method, which we override to call the Command.__init__ method. That then calls
127
our run method which is pretty straight forward.
129
The only wrinkle is that we have to map bzr's dictionary of options and arguments
130
back into command line options and arguments for the script.
133
def find_command(cls, cmd):
134
bzrpath = os.environ.get('BZRPATH', '')
136
for dir in bzrpath.split(':'):
137
path = os.path.join(dir, cmd)
138
if os.path.isfile(path):
139
return ExternalCommand(path)
143
find_command = classmethod(find_command)
145
def __init__(self, path):
148
# TODO: If either of these fail, we should detect that and
149
# assume that path is not really a bzr plugin after all.
151
pipe = os.popen('%s --bzr-usage' % path, 'r')
152
self.takes_options = pipe.readline().split()
153
self.takes_args = pipe.readline().split()
156
pipe = os.popen('%s --bzr-help' % path, 'r')
157
self.__doc__ = pipe.read()
160
def __call__(self, options, arguments):
161
Command.__init__(self, options, arguments)
164
def run(self, **kargs):
172
if OPTIONS.has_key(name):
174
opts.append('--%s' % name)
175
if value is not None and value is not True:
176
opts.append(str(value))
178
# it's an arg, or arg list
179
if type(value) is not list:
185
self.status = os.spawnv(os.P_WAIT, self.path, [self.path] + opts + args)
164
189
class cmd_status(Command):
165
190
"""Display status summary.
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.
192
This reports on versioned and unknown files, reporting them
193
grouped by state. Possible states are:
196
Versioned in the working copy but not in the previous revision.
199
Versioned in the previous revision but removed or deleted
203
Path of this file changed from the previous revision;
204
the text may also have changed. This includes files whose
205
parent directory was renamed.
208
Text has changed since the previous revision.
211
Nothing about this file has changed since the previous revision.
212
Only shown with --all.
215
Not versioned and not matching an ignore pattern.
217
To see ignored files use 'bzr ignored'. For details in the
218
changes to file texts, use 'bzr diff'.
220
If no arguments are specified, the status of the entire working
221
directory is shown. Otherwise, only the status of the specified
222
files or directories is reported. If a directory is given, status
223
is reported for everything inside that directory.
171
takes_options = ['all']
225
takes_args = ['file*']
226
takes_options = ['all', 'show-ids']
227
aliases = ['st', 'stat']
173
def run(self, all=False):
174
#import bzrlib.status
175
#bzrlib.status.tree_status(Branch('.'))
176
Branch('.').show_status(show_all=all)
229
def run(self, all=False, show_ids=False, file_list=None):
231
b = Branch(file_list[0], lock_mode='r')
232
file_list = [b.relpath(x) for x in file_list]
233
# special case: only one path was given and it's the root
235
if file_list == ['']:
238
b = Branch('.', lock_mode='r')
240
status.show_status(b, show_unchanged=all, show_ids=show_ids,
241
specific_files=file_list)
179
244
class cmd_cat_revision(Command):
526
class cmd_modified(Command):
527
"""List files modified in working tree."""
532
inv = b.read_working_inventory()
533
sc = statcache.update_cache(b, inv)
534
basis = b.basis_tree()
535
basis_inv = basis.inventory
537
# We used to do this through iter_entries(), but that's slow
538
# when most of the files are unmodified, as is usually the
539
# case. So instead we iterate by inventory entry, and only
540
# calculate paths as necessary.
542
for file_id in basis_inv:
543
cacheentry = sc.get(file_id)
544
if not cacheentry: # deleted
546
ie = basis_inv[file_id]
547
if cacheentry[statcache.SC_SHA1] != ie.text_sha1:
548
path = inv.id2path(file_id)
553
class cmd_added(Command):
554
"""List files added in working tree."""
558
wt = b.working_tree()
559
basis_inv = b.basis_tree().inventory
562
if file_id in basis_inv:
564
path = inv.id2path(file_id)
565
if not os.access(b.abspath(path), os.F_OK):
451
571
class cmd_root(Command):
452
572
"""Show the tree root directory.
456
576
takes_args = ['filename?']
457
577
def run(self, filename=None):
458
578
"""Print the branch root."""
459
print bzrlib.branch.find_branch_root(filename)
579
from branch import find_branch
580
b = find_branch(filename)
581
print getattr(b, 'base', None) or getattr(b, 'baseurl')
463
584
class cmd_log(Command):
464
585
"""Show log of this branch.
466
TODO: Options to show ids; to limit range; etc.
587
TODO: Option to limit range.
589
TODO: Option to show in forward order.
468
takes_options = ['timezone', 'verbose']
469
def run(self, timezone='original', verbose=False):
470
Branch('.').write_log(show_timezone=timezone, verbose=verbose)
591
takes_args = ['filename?']
592
takes_options = ['timezone', 'verbose', 'show-ids']
593
def run(self, filename=None, timezone='original', verbose=False, show_ids=False):
594
from bzrlib import show_log, find_branch
597
b = find_branch(filename, lock_mode='r')
598
fp = b.relpath(filename)
600
file_id = b.read_working_inventory().path2id(fp)
602
file_id = None # points to branch root
604
b = find_branch('.', lock_mode='r')
608
show_timezone=timezone,
615
class cmd_touching_revisions(Command):
616
"""Return revision-ids which affected a particular file.
618
A more user-friendly interface is "bzr log FILE"."""
620
takes_args = ["filename"]
621
def run(self, filename):
622
b = Branch(filename, lock_mode='r')
623
inv = b.read_working_inventory()
624
file_id = inv.path2id(b.relpath(filename))
625
for revno, revision_id, what in bzrlib.log.find_touching_revisions(b, file_id):
626
print "%6d %s" % (revno, what)
473
629
class cmd_ls(Command):
509
665
class cmd_ignore(Command):
510
"""Ignore a command or pattern"""
666
"""Ignore a command or pattern
668
To remove patterns from the ignore list, edit the .bzrignore file.
670
If the pattern contains a slash, it is compared to the whole path
671
from the branch root. Otherwise, it is comapred to only the last
672
component of the path.
674
Ignore patterns are case-insensitive on case-insensitive systems.
676
Note: wildcards must be quoted from the shell on Unix.
679
bzr ignore ./Makefile
511
682
takes_args = ['name_pattern']
513
684
def run(self, name_pattern):
685
from bzrlib.atomicfile import AtomicFile
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')
689
ifn = b.abspath('.bzrignore')
691
if os.path.exists(ifn):
694
igns = f.read().decode('utf-8')
700
if igns and igns[-1] != '\n':
702
igns += name_pattern + '\n'
705
f = AtomicFile(ifn, 'wt')
706
f.write(igns.encode('utf-8'))
521
711
inv = b.working_tree().inventory
522
712
if inv.path2id('.bzrignore'):
596
789
class cmd_commit(Command):
597
790
"""Commit changes into a new revision.
599
TODO: Commit only selected files.
792
If selected files are specified, only changes to those files are
793
committed. If a directory is specified then its contents are also
796
A selected-file commit may fail in some cases where the committed
797
tree would be invalid, such as trying to commit a file in a
798
newly-added directory that is not itself committed.
601
800
TODO: Run hooks on tree to-be-committed, and after commit.
603
802
TODO: Strict commit that fails if there are unknown or deleted files.
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)
804
takes_args = ['selected*']
805
takes_options = ['message', 'file', 'verbose']
806
aliases = ['ci', 'checkin']
808
def run(self, message=None, file=None, verbose=True, selected_list=None):
809
from bzrlib.commit import commit
811
## Warning: shadows builtin file()
812
if not message and not file:
813
raise BzrCommandError("please specify a commit message",
814
["use either --message or --file"])
815
elif message and file:
816
raise BzrCommandError("please specify either --message or --file")
820
message = codecs.open(file, 'rt', bzrlib.user_encoding).read()
823
commit(b, message, verbose=verbose, specific_files=selected_list)
613
826
class cmd_check(Command):
697
931
For a list of all available commands, say 'bzr help commands'."""
698
932
takes_args = ['topic?']
700
935
def run(self, topic=None):
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
help_on_option(cmdclass.takes_options)
747
def help_on_option(options):
755
for shortname, longname in SHORT_OPTIONS.items():
757
l += ', -' + shortname
763
"""List all commands"""
767
for k, v in globals().items():
768
if k.startswith('cmd_'):
769
accu.append((k[4:].replace('_','-'), v))
771
for cmdname, cmdclass in accu:
775
help = inspect.getdoc(cmdclass)
777
print " " + help.split('\n', 1)[0]
940
class cmd_update_stat_cache(Command):
941
"""Update stat-cache mapping inodes to SHA-1 hashes.
948
statcache.update_cache(b.base, b.read_working_inventory())
780
951
######################################################################
1026
1200
msg = 'assertion failed'
1028
1202
msg += ': ' + str(e)
1029
_report_exception(e, msg)
1203
_report_exception(msg)
1031
1205
except KeyboardInterrupt, e:
1032
_report_exception(e, 'interrupted', quiet=True)
1206
_report_exception('interrupted', quiet=True)
1034
1208
except Exception, e:
1036
if isinstance(e, IOError) and e.errno == errno.EPIPE:
1210
if (isinstance(e, IOError)
1211
and hasattr(e, 'errno')
1212
and e.errno == errno.EPIPE):
1038
1214
msg = 'broken pipe'
1040
1216
msg = str(e).rstrip('\n')
1041
_report_exception(e, msg, quiet)
1217
_report_exception(msg, quiet)
1044
1220
bzrlib.trace.close_trace()