bzr branch
http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
| 485
by Martin Pool - move commit code into its own module | 1 | # Copyright (C) 2005 Canonical Ltd
 | 
| 2 | ||
| 3 | # This program is free software; you can redistribute it and/or modify
 | |
| 4 | # it under the terms of the GNU General Public License as published by
 | |
| 5 | # the Free Software Foundation; either version 2 of the License, or
 | |
| 6 | # (at your option) any later version.
 | |
| 7 | ||
| 8 | # This program is distributed in the hope that it will be useful,
 | |
| 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
| 11 | # GNU General Public License for more details.
 | |
| 12 | ||
| 13 | # You should have received a copy of the GNU General Public License
 | |
| 14 | # along with this program; if not, write to the Free Software
 | |
| 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 | |
| 16 | ||
| 17 | ||
| 18 | ||
| 815
by Martin Pool - track pending-merges | 19 | # FIXME: "bzr commit doc/format" commits doc/format.txt!
 | 
| 20 | ||
| 517
by Martin Pool - cleanup | 21 | def commit(branch, message, | 
| 22 | timestamp=None, | |
| 23 | timezone=None, | |
| 485
by Martin Pool - move commit code into its own module | 24 | committer=None, | 
| 491
by Martin Pool - Selective commit! | 25 | verbose=True, | 
| 494
by Martin Pool - commit takes an optional caller-specified revision id | 26 | specific_files=None, | 
| 882
by Martin Pool - Optionally raise EmptyCommit if there are no changes. Test for this. | 27 | rev_id=None, | 
| 885
by Martin Pool - commit command refuses unless something is changed or --unchanged is given | 28 | allow_pointless=True): | 
| 485
by Martin Pool - move commit code into its own module | 29 | """Commit working copy as a new revision. | 
| 30 | ||
| 31 |     The basic approach is to add all the file texts into the
 | |
| 32 |     store, then the inventory, then make a new revision pointing
 | |
| 33 |     to that inventory and store that.
 | |
| 34 | ||
| 35 |     This is not quite safe if the working copy changes during the
 | |
| 36 |     commit; for the moment that is simply not allowed.  A better
 | |
| 37 |     approach is to make a temporary copy of the files before
 | |
| 38 |     computing their hashes, and then add those hashes in turn to
 | |
| 39 |     the inventory.  This should mean at least that there are no
 | |
| 40 |     broken hash pointers.  There is no way we can get a snapshot
 | |
| 41 |     of the whole directory at an instant.  This would also have to
 | |
| 42 |     be robust against files disappearing, moving, etc.  So the
 | |
| 43 |     whole thing is a bit hard.
 | |
| 44 | ||
| 885
by Martin Pool - commit command refuses unless something is changed or --unchanged is given | 45 |     This raises PointlessCommit if there are no changes, no new merges,
 | 
| 46 |     and allow_pointless  is false.
 | |
| 882
by Martin Pool - Optionally raise EmptyCommit if there are no changes. Test for this. | 47 | |
| 485
by Martin Pool - move commit code into its own module | 48 |     timestamp -- if not None, seconds-since-epoch for a
 | 
| 49 |          postdated/predated commit.
 | |
| 491
by Martin Pool - Selective commit! | 50 | |
| 51 |     specific_files
 | |
| 52 |         If true, commit only those files.
 | |
| 504
by Martin Pool doc | 53 | |
| 54 |     rev_id
 | |
| 55 |         If set, use this as the new revision id.
 | |
| 56 |         Useful for test or import commands that need to tightly
 | |
| 57 |         control what revisions are assigned.  If you duplicate
 | |
| 58 |         a revision id that exists elsewhere it is your own fault.
 | |
| 59 |         If null (default), a time/random revision id is generated.
 | |
| 485
by Martin Pool - move commit code into its own module | 60 |     """
 | 
| 61 | ||
| 632
by Martin Pool - refactor commit code | 62 | import time, tempfile | 
| 485
by Martin Pool - move commit code into its own module | 63 | |
| 697
by Martin Pool - write out parent list for new revisions | 64 | from bzrlib.osutils import local_time_offset, username | 
| 65 | from bzrlib.branch import gen_file_id | |
| 885
by Martin Pool - commit command refuses unless something is changed or --unchanged is given | 66 | from bzrlib.errors import BzrError, PointlessCommit | 
| 697
by Martin Pool - write out parent list for new revisions | 67 | from bzrlib.revision import Revision, RevisionReference | 
| 68 | from bzrlib.trace import mutter, note | |
| 802
by Martin Pool - Remove XMLMixin class in favour of simple pack_xml, unpack_xml functions | 69 | from bzrlib.xml import pack_xml | 
| 485
by Martin Pool - move commit code into its own module | 70 | |
| 610
by Martin Pool - replace Branch.lock(mode) with separate lock_read and lock_write | 71 | branch.lock_write() | 
| 580
by Martin Pool - Use explicit lock methods on a branch, rather than doing it | 72 | |
| 73 | try: | |
| 74 |         # First walk over the working inventory; and both update that
 | |
| 75 |         # and also build a new revision inventory.  The revision
 | |
| 76 |         # inventory needs to hold the text-id, sha1 and size of the
 | |
| 77 |         # actual file versions committed in the revision.  (These are
 | |
| 78 |         # not present in the working inventory.)  We also need to
 | |
| 79 |         # detect missing/deleted files, and remove them from the
 | |
| 80 |         # working inventory.
 | |
| 81 | ||
| 82 | work_tree = branch.working_tree() | |
| 83 | work_inv = work_tree.inventory | |
| 84 | basis = branch.basis_tree() | |
| 85 | basis_inv = basis.inventory | |
| 86 | ||
| 87 | if verbose: | |
| 88 | note('looking for changes...') | |
| 89 | ||
| 815
by Martin Pool - track pending-merges | 90 | pending_merges = branch.pending_merges() | 
| 91 | ||
| 882
by Martin Pool - Optionally raise EmptyCommit if there are no changes. Test for this. | 92 | missing_ids, new_inv, any_changes = \ | 
| 93 | _gather_commit(branch, | |
| 94 | work_tree, | |
| 95 | work_inv, | |
| 96 | basis_inv, | |
| 97 | specific_files, | |
| 98 | verbose) | |
| 99 | ||
| 885
by Martin Pool - commit command refuses unless something is changed or --unchanged is given | 100 | if not (any_changes or allow_pointless or pending_merges): | 
| 101 | raise PointlessCommit() | |
| 580
by Martin Pool - Use explicit lock methods on a branch, rather than doing it | 102 | |
| 103 | for file_id in missing_ids: | |
| 104 |             # Any files that have been deleted are now removed from the
 | |
| 105 |             # working inventory.  Files that were not selected for commit
 | |
| 106 |             # are left as they were in the working inventory and ommitted
 | |
| 107 |             # from the revision inventory.
 | |
| 108 | ||
| 109 |             # have to do this later so we don't mess up the iterator.
 | |
| 110 |             # since parents may be removed before their children we
 | |
| 111 |             # have to test.
 | |
| 112 | ||
| 113 |             # FIXME: There's probably a better way to do this; perhaps
 | |
| 114 |             # the workingtree should know how to filter itbranch.
 | |
| 115 | if work_inv.has_id(file_id): | |
| 116 | del work_inv[file_id] | |
| 117 | ||
| 118 | ||
| 119 | if rev_id is None: | |
| 120 | rev_id = _gen_revision_id(time.time()) | |
| 121 | inv_id = rev_id | |
| 122 | ||
| 123 | inv_tmp = tempfile.TemporaryFile() | |
| 802
by Martin Pool - Remove XMLMixin class in favour of simple pack_xml, unpack_xml functions | 124 | pack_xml(new_inv, inv_tmp) | 
| 580
by Martin Pool - Use explicit lock methods on a branch, rather than doing it | 125 | inv_tmp.seek(0) | 
| 126 | branch.inventory_store.add(inv_tmp, inv_id) | |
| 127 | mutter('new inventory_id is {%s}' % inv_id) | |
| 128 | ||
| 672
by Martin Pool - revision records include the hash of their inventory and | 129 |         # We could also just sha hash the inv_tmp file
 | 
| 130 |         # however, in the case that branch.inventory_store.add()
 | |
| 131 |         # ever actually does anything special
 | |
| 132 | inv_sha1 = branch.get_inventory_sha1(inv_id) | |
| 133 | ||
| 580
by Martin Pool - Use explicit lock methods on a branch, rather than doing it | 134 | branch._write_inventory(work_inv) | 
| 135 | ||
| 136 | if timestamp == None: | |
| 137 | timestamp = time.time() | |
| 138 | ||
| 139 | if committer == None: | |
| 140 | committer = username() | |
| 141 | ||
| 142 | if timezone == None: | |
| 143 | timezone = local_time_offset() | |
| 144 | ||
| 145 | mutter("building commit log message") | |
| 146 | rev = Revision(timestamp=timestamp, | |
| 147 | timezone=timezone, | |
| 148 | committer=committer, | |
| 149 | message = message, | |
| 150 | inventory_id=inv_id, | |
| 672
by Martin Pool - revision records include the hash of their inventory and | 151 | inventory_sha1=inv_sha1, | 
| 580
by Martin Pool - Use explicit lock methods on a branch, rather than doing it | 152 | revision_id=rev_id) | 
| 717
by Martin Pool - correctly set parent list when committing first | 153 | |
| 816
by Martin Pool - don't write precursor field in new revision xml | 154 | rev.parents = [] | 
| 717
by Martin Pool - correctly set parent list when committing first | 155 | precursor_id = branch.last_patch() | 
| 156 | if precursor_id: | |
| 157 | precursor_sha1 = branch.get_revision_sha1(precursor_id) | |
| 816
by Martin Pool - don't write precursor field in new revision xml | 158 | rev.parents.append(RevisionReference(precursor_id, precursor_sha1)) | 
| 159 | for merge_rev in pending_merges: | |
| 160 | rev.parents.append(RevisionReference(merge_rev)) | |
| 580
by Martin Pool - Use explicit lock methods on a branch, rather than doing it | 161 | |
| 162 | rev_tmp = tempfile.TemporaryFile() | |
| 802
by Martin Pool - Remove XMLMixin class in favour of simple pack_xml, unpack_xml functions | 163 | pack_xml(rev, rev_tmp) | 
| 580
by Martin Pool - Use explicit lock methods on a branch, rather than doing it | 164 | rev_tmp.seek(0) | 
| 165 | branch.revision_store.add(rev_tmp, rev_id) | |
| 166 | mutter("new revision_id is {%s}" % rev_id) | |
| 167 | ||
| 168 |         ## XXX: Everything up to here can simply be orphaned if we abort
 | |
| 169 |         ## the commit; it will leave junk files behind but that doesn't
 | |
| 170 |         ## matter.
 | |
| 171 | ||
| 172 |         ## TODO: Read back the just-generated changeset, and make sure it
 | |
| 173 |         ## applies and recreates the right state.
 | |
| 174 | ||
| 175 |         ## TODO: Also calculate and store the inventory SHA1
 | |
| 176 | mutter("committing patch r%d" % (branch.revno() + 1)) | |
| 177 | ||
| 178 | branch.append_revision(rev_id) | |
| 179 | ||
| 818
by Martin Pool - Clear pending-merge list when committing. | 180 | branch.set_pending_merges([]) | 
| 181 | ||
| 580
by Martin Pool - Use explicit lock methods on a branch, rather than doing it | 182 | if verbose: | 
| 183 | note("commited r%d" % branch.revno()) | |
| 184 | finally: | |
| 185 | branch.unlock() | |
| 485
by Martin Pool - move commit code into its own module | 186 | |
| 187 | ||
| 188 | ||
| 189 | def _gen_revision_id(when): | |
| 190 | """Return new revision-id.""" | |
| 191 | from binascii import hexlify | |
| 192 | from osutils import rand_bytes, compact_date, user_email | |
| 193 | ||
| 194 | s = '%s-%s-' % (user_email(), compact_date(when)) | |
| 195 | s += hexlify(rand_bytes(8)) | |
| 196 | return s | |
| 197 | ||
| 198 | ||
| 632
by Martin Pool - refactor commit code | 199 | def _gather_commit(branch, work_tree, work_inv, basis_inv, specific_files, | 
| 200 | verbose): | |
| 201 | """Build inventory preparatory to commit. | |
| 202 | ||
| 882
by Martin Pool - Optionally raise EmptyCommit if there are no changes. Test for this. | 203 |     Returns missing_ids, new_inv, any_changes.
 | 
| 204 | ||
| 632
by Martin Pool - refactor commit code | 205 |     This adds any changed files into the text store, and sets their
 | 
| 206 |     test-id, sha and size in the returned inventory appropriately.
 | |
| 207 | ||
| 208 |     missing_ids
 | |
| 209 |         Modified to hold a list of files that have been deleted from
 | |
| 210 |         the working directory; these should be removed from the
 | |
| 211 |         working inventory.
 | |
| 212 |     """
 | |
| 213 | from bzrlib.inventory import Inventory | |
| 958
by Martin Pool - tidy up imports to use full module names | 214 | from bzrlib.osutils import isdir, isfile, sha_string, quotefn, \ | 
| 632
by Martin Pool - refactor commit code | 215 | local_time_offset, username, kind_marker, is_inside_any | 
| 216 | ||
| 958
by Martin Pool - tidy up imports to use full module names | 217 | from bzrlib.branch import gen_file_id | 
| 218 | from bzrlib.errors import BzrError | |
| 219 | from bzrlib.revision import Revision | |
| 632
by Martin Pool - refactor commit code | 220 | from bzrlib.trace import mutter, note | 
| 221 | ||
| 882
by Martin Pool - Optionally raise EmptyCommit if there are no changes. Test for this. | 222 | any_changes = False | 
| 909
by Martin Pool - merge John's code to give the tree root an explicit file id | 223 | inv = Inventory(work_inv.root.file_id) | 
| 632
by Martin Pool - refactor commit code | 224 | missing_ids = [] | 
| 225 | ||
| 226 | for path, entry in work_inv.iter_entries(): | |
| 227 |         ## TODO: Check that the file kind has not changed from the previous
 | |
| 228 |         ## revision of this file (if any).
 | |
| 229 | ||
| 230 | p = branch.abspath(path) | |
| 231 | file_id = entry.file_id | |
| 232 | mutter('commit prep file %s, id %r ' % (p, file_id)) | |
| 233 | ||
| 234 | if specific_files and not is_inside_any(specific_files, path): | |
| 880
by Martin Pool trace | 235 | mutter(' skipping file excluded from commit') | 
| 632
by Martin Pool - refactor commit code | 236 | if basis_inv.has_id(file_id): | 
| 237 |                 # carry over with previous state
 | |
| 238 | inv.add(basis_inv[file_id].copy()) | |
| 239 | else: | |
| 240 |                 # omit this from committed inventory
 | |
| 241 |                 pass
 | |
| 242 |             continue
 | |
| 243 | ||
| 244 | if not work_tree.has_id(file_id): | |
| 245 | if verbose: | |
| 246 | print('deleted %s%s' % (path, kind_marker(entry.kind))) | |
| 882
by Martin Pool - Optionally raise EmptyCommit if there are no changes. Test for this. | 247 | any_changes = True | 
| 632
by Martin Pool - refactor commit code | 248 | mutter(" file is missing, removing from inventory") | 
| 249 | missing_ids.append(file_id) | |
| 250 |             continue
 | |
| 251 | ||
| 633
by Martin Pool - Show added/renamed/modified messages from commit for non-file | 252 |         # this is present in the new inventory; may be new, modified or
 | 
| 253 |         # unchanged.
 | |
| 254 | old_ie = basis_inv.has_id(file_id) and basis_inv[file_id] | |
| 255 | ||
| 256 | entry = entry.copy() | |
| 632
by Martin Pool - refactor commit code | 257 | inv.add(entry) | 
| 258 | ||
| 633
by Martin Pool - Show added/renamed/modified messages from commit for non-file | 259 | if old_ie: | 
| 260 | old_kind = old_ie.kind | |
| 632
by Martin Pool - refactor commit code | 261 | if old_kind != entry.kind: | 
| 262 | raise BzrError("entry %r changed kind from %r to %r" | |
| 263 | % (file_id, old_kind, entry.kind)) | |
| 264 | ||
| 265 | if entry.kind == 'directory': | |
| 266 | if not isdir(p): | |
| 267 | raise BzrError("%s is entered as directory but not a directory" | |
| 268 | % quotefn(p)) | |
| 269 | elif entry.kind == 'file': | |
| 270 | if not isfile(p): | |
| 271 | raise BzrError("%s is entered as file but is not a file" % quotefn(p)) | |
| 272 | ||
| 273 | new_sha1 = work_tree.get_file_sha1(file_id) | |
| 274 | ||
| 275 | if (old_ie | |
| 276 | and old_ie.text_sha1 == new_sha1): | |
| 277 |                 ## assert content == basis.get_file(file_id).read()
 | |
| 278 | entry.text_id = old_ie.text_id | |
| 279 | entry.text_sha1 = new_sha1 | |
| 280 | entry.text_size = old_ie.text_size | |
| 281 | mutter(' unchanged from previous text_id {%s}' % | |
| 282 | entry.text_id) | |
| 283 | else: | |
| 284 | content = file(p, 'rb').read() | |
| 285 | ||
| 286 |                 # calculate the sha again, just in case the file contents
 | |
| 287 |                 # changed since we updated the cache
 | |
| 288 | entry.text_sha1 = sha_string(content) | |
| 289 | entry.text_size = len(content) | |
| 290 | ||
| 291 | entry.text_id = gen_file_id(entry.name) | |
| 292 | branch.text_store.add(content, entry.text_id) | |
| 293 | mutter(' stored with text_id {%s}' % entry.text_id) | |
| 633
by Martin Pool - Show added/renamed/modified messages from commit for non-file | 294 | |
| 295 | if verbose: | |
| 296 | marked = path + kind_marker(entry.kind) | |
| 297 | if not old_ie: | |
| 298 | print 'added', marked | |
| 882
by Martin Pool - Optionally raise EmptyCommit if there are no changes. Test for this. | 299 | any_changes = True | 
| 633
by Martin Pool - Show added/renamed/modified messages from commit for non-file | 300 | elif old_ie == entry: | 
| 301 | pass # unchanged | |
| 302 | elif (old_ie.name == entry.name | |
| 303 | and old_ie.parent_id == entry.parent_id): | |
| 304 | print 'modified', marked | |
| 882
by Martin Pool - Optionally raise EmptyCommit if there are no changes. Test for this. | 305 | any_changes = True | 
| 633
by Martin Pool - Show added/renamed/modified messages from commit for non-file | 306 | else: | 
| 307 | print 'renamed', marked | |
| 882
by Martin Pool - Optionally raise EmptyCommit if there are no changes. Test for this. | 308 | any_changes = True | 
| 632
by Martin Pool - refactor commit code | 309 | |
| 882
by Martin Pool - Optionally raise EmptyCommit if there are no changes. Test for this. | 310 | return missing_ids, inv, any_changes | 
| 633
by Martin Pool - Show added/renamed/modified messages from commit for non-file | 311 | |
| 312 |