26
26
from trace import mutter, note
27
27
from tree import Tree, EmptyTree, RevisionTree, WorkingTree
28
28
from inventory import InventoryEntry, Inventory
29
from osutils import isdir, quotefn, isfile, uuid, sha_file, username, chomp, \
29
from osutils import isdir, quotefn, isfile, uuid, sha_file, username, \
30
30
format_date, compact_date, pumpfile, user_email, rand_bytes, splitpath, \
31
31
joinpath, sha_string, file_kind, local_time_offset, appendpath
32
32
from store import ImmutableStore
74
74
"""Branch holding a history of revisions.
76
:todo: Perhaps use different stores for different classes of object,
76
TODO: Perhaps use different stores for different classes of object,
77
77
so that we can keep track of how much space each one uses,
78
78
or garbage-collect them.
80
:todo: Add a RemoteBranch subclass. For the basic case of read-only
80
TODO: Add a RemoteBranch subclass. For the basic case of read-only
81
81
HTTP access this should be very easy by,
82
82
just redirecting controlfile access into HTTP requests.
83
83
We would need a RemoteStore working similarly.
85
:todo: Keep the on-disk branch locked while the object exists.
85
TODO: Keep the on-disk branch locked while the object exists.
87
:todo: mkdir() method.
89
89
def __init__(self, base, init=False, find_root=True):
90
90
"""Create new branch object at a particular location.
92
:param base: Base directory for the branch.
92
base -- Base directory for the branch.
94
:param init: If True, create new control files in a previously
94
init -- If True, create new control files in a previously
95
95
unversioned directory. If False, the branch must already
98
:param find_root: If true and init is false, find the root of the
98
find_root -- If true and init is false, find the root of the
99
99
existing branch containing base.
101
101
In the test suite, creation of new trees is tested using the
154
154
def controlfile(self, file_or_path, mode='r'):
155
"""Open a control file for this branch"""
156
return file(self.controlfilename(file_or_path), mode)
155
"""Open a control file for this branch.
157
There are two classes of file in the control directory: text
158
and binary. binary files are untranslated byte streams. Text
159
control files are stored with Unix newlines and in UTF-8, even
160
if the platform or locale defaults are different.
163
fn = self.controlfilename(file_or_path)
165
if mode == 'rb' or mode == 'wb':
166
return file(fn, mode)
167
elif mode == 'r' or mode == 'w':
168
# open in binary mode anyhow so there's no newline translation;
169
# codecs uses line buffering by default; don't want that.
171
return codecs.open(fn, mode + 'b', 'utf-8',
174
raise BzrError("invalid controlfile mode %r" % mode)
159
178
def _make_control(self):
161
180
self.controlfile('README', 'w').write(
162
181
"This is a Bazaar-NG control directory.\n"
163
182
"Do not change any files in this directory.")
164
self.controlfile('branch-format', 'wb').write(BZR_BRANCH_FORMAT)
183
self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT)
165
184
for d in ('text-store', 'inventory-store', 'revision-store'):
166
185
os.mkdir(self.controlfilename(d))
167
186
for f in ('revision-history', 'merged-patches',
182
201
# This ignores newlines so that we can open branches created
183
202
# on Windows from Linux and so on. I think it might be better
184
203
# to always make all internal files in unix format.
185
fmt = self.controlfile('branch-format', 'rb').read()
204
fmt = self.controlfile('branch-format', 'r').read()
186
205
fmt.replace('\r\n', '')
187
206
if fmt != BZR_BRANCH_FORMAT:
188
207
bailout('sorry, branch format %r not supported' % fmt,
193
212
def read_working_inventory(self):
194
213
"""Read the working inventory."""
195
214
before = time.time()
196
inv = Inventory.read_xml(self.controlfile('inventory', 'r'))
215
# ElementTree does its own conversion from UTF-8, so open in
217
inv = Inventory.read_xml(self.controlfile('inventory', 'rb'))
197
218
mutter("loaded inventory of %d items in %f"
198
219
% (len(inv), time.time() - before))
208
229
## TODO: factor out to atomicfile? is rename safe on windows?
209
230
## TODO: Maybe some kind of clean/dirty marker on inventory?
210
231
tmpfname = self.controlfilename('inventory.tmp')
211
tmpf = file(tmpfname, 'w')
232
tmpf = file(tmpfname, 'wb')
212
233
inv.write_xml(tmpf)
214
235
inv_fname = self.controlfilename('inventory')
225
246
def add(self, files, verbose=False):
226
247
"""Make files versioned.
249
Note that the command line normally calls smart_add instead.
228
251
This puts the files in the Added state, so that they will be
229
252
recorded by the next commit.
231
:todo: Perhaps have an option to add the ids even if the files do
254
TODO: Perhaps have an option to add the ids even if the files do
234
:todo: Perhaps return the ids of the files? But then again it
257
TODO: Perhaps return the ids of the files? But then again it
235
258
is easy to retrieve them if they're needed.
237
:todo: Option to specify file id.
260
TODO: Option to specify file id.
239
:todo: Adding a directory should optionally recurse down and
262
TODO: Adding a directory should optionally recurse down and
240
263
add all non-ignored children. Perhaps do that in a
241
264
higher-level method.
316
339
This does not remove their text. This does not run on
318
:todo: Refuse to remove modified files unless --force is given?
341
TODO: Refuse to remove modified files unless --force is given?
320
343
>>> b = ScratchBranch(files=['foo'])
339
362
>>> b.working_tree().has_filename('foo')
342
:todo: Do something useful with directories.
365
TODO: Do something useful with directories.
344
:todo: Should this remove the text or not? Tough call; not
367
TODO: Should this remove the text or not? Tough call; not
345
368
removing may be useful and the user can just use use rm, and
346
369
is the opposite of add. Removing it is consistent with most
347
370
other tools. Maybe an option.
411
434
be robust against files disappearing, moving, etc. So the
412
435
whole thing is a bit hard.
414
:param timestamp: if not None, seconds-since-epoch for a
437
timestamp -- if not None, seconds-since-epoch for a
415
438
postdated/predated commit.
555
578
## TODO: Also calculate and store the inventory SHA1
556
579
mutter("committing patch r%d" % (self.revno() + 1))
558
mutter("append to revision-history")
559
f = self.controlfile('revision-history', 'at')
560
f.write(rev_id + '\n')
582
self.append_revision(rev_id)
564
585
note("commited r%d" % self.revno())
588
def append_revision(self, revision_id):
589
mutter("add {%s} to revision-history" % revision_id)
590
rev_history = self.revision_history()
592
tmprhname = self.controlfilename('revision-history.tmp')
593
rhname = self.controlfilename('revision-history')
595
f = file(tmprhname, 'wt')
596
rev_history.append(revision_id)
597
f.write('\n'.join(rev_history))
601
if sys.platform == 'win32':
603
os.rename(tmprhname, rhname)
567
607
def get_revision(self, revision_id):
568
608
"""Return the Revision object for a named revision"""
569
609
r = Revision.read_xml(self.revision_store[revision_id])
574
614
def get_inventory(self, inventory_id):
575
615
"""Get Inventory object by hash.
577
:todo: Perhaps for this and similar methods, take a revision
617
TODO: Perhaps for this and similar methods, take a revision
578
618
parameter which can be either an integer revno or a
580
620
i = Inventory.read_xml(self.inventory_store[inventory_id])
683
def write_log(self, show_timezone='original'):
723
def write_log(self, show_timezone='original', verbose=False):
684
724
"""Write out human-readable log of commits to this branch
686
:param utc: If true, show dates in universal time, not local time."""
726
utc -- If true, show dates in universal time, not local time."""
687
727
## TODO: Option to choose either original, utc or local timezone
708
748
for l in rev.message.split('\n'):
751
if verbose == True and precursor != None:
752
print 'changed files:'
753
tree = self.revision_tree(p)
754
prevtree = self.revision_tree(precursor)
756
for file_state, fid, old_name, new_name, kind in \
757
diff_trees(prevtree, tree, ):
758
if file_state == 'A' or file_state == 'M':
759
show_status(file_state, kind, new_name)
760
elif file_state == 'D':
761
show_status(file_state, kind, old_name)
762
elif file_state == 'R':
763
show_status(file_state, kind,
764
old_name + ' => ' + new_name)
715
770
def rename_one(self, from_rel, to_rel):
773
This can change the directory or the filename or both.
716
775
tree = self.working_tree()
717
776
inv = tree.inventory
718
777
if not tree.has_filename(from_rel):
871
926
show_status(fs, kind, newname)
873
bailout("wierd file state %r" % ((fs, fid),))
928
bailout("weird file state %r" % ((fs, fid),))