~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to bzrtools.py

  • Committer: abentley
  • Date: 2005-10-15 01:00:06 UTC
  • Revision ID: abentley@lappy-20051015010006-ed880d280fb52391
Added --dry-run, --detrius options to clean-tree

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
#    along with this program; if not, write to the Free Software
16
16
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
17
import bzrlib
 
18
import bzrlib.errors
18
19
import os
19
20
import os.path
20
21
import sys
21
22
import tempfile
22
23
import shutil
 
24
import errno
23
25
from subprocess import Popen, PIPE
 
26
import codecs
24
27
 
25
28
def temp_branch():
26
29
    dirname = tempfile.mkdtemp("temp-branch")
27
 
    return bzrlib.Branch(dirname, init=True)
 
30
    return bzrlib.branch.Branch.initialize(dirname)
28
31
 
29
32
def rm_branch(br):
30
33
    shutil.rmtree(br.base)
32
35
def is_clean(cur_branch):
33
36
    """
34
37
    Return true if no files are modifed or unknown
 
38
    >>> import bzrlib.add
35
39
    >>> br = temp_branch()
36
40
    >>> is_clean(br)
37
 
    True
 
41
    (True, [])
38
42
    >>> fooname = os.path.join(br.base, "foo")
39
43
    >>> file(fooname, "wb").write("bar")
40
44
    >>> is_clean(br)
41
 
    False
42
 
    >>> bzrlib.add.smart_add([fooname])
 
45
    (True, ['foo'])
 
46
    >>> bzrlib.add.smart_add_branch(br, [br.base])
 
47
    1
43
48
    >>> is_clean(br)
44
 
    False
 
49
    (False, [])
45
50
    >>> br.commit("added file")
46
51
    >>> is_clean(br)
47
 
    True
 
52
    (True, [])
48
53
    >>> rm_branch(br)
49
54
    """
50
55
    from bzrlib.diff import compare_trees
51
56
    old_tree = cur_branch.basis_tree()
52
57
    new_tree = cur_branch.working_tree()
53
58
    non_source = []
54
 
    for path, file_class, kind, file_id in new_tree.list_files():
 
59
    for path, file_class, kind, file_id, entry in new_tree.list_files():
55
60
        if file_class in ('?', 'I'):
56
61
            non_source.append(path)
57
62
    delta = compare_trees(old_tree, new_tree, want_unchanged=False)
120
125
    arg_str = " ".join([shell_escape(a) for a in args])
121
126
    return os.system(arg_str)
122
127
 
123
 
def rsync(source, target, ssh=False, excludes=()):
124
 
    """
125
 
    >>> real_system = os.system
126
 
    >>> os.system = sys.stdout.write
127
 
    >>> rsync("a", "b")
128
 
    \\r\\s\\y\\n\\c \\-\\a\\v \\-\\-\\d\\e\\l\\e\\t\\e \\a \\b
129
 
    >>> rsync("a", "b", excludes=("*.py",))
130
 
    \\r\\s\\y\\n\\c \\-\\a\\v \\-\\-\\d\\e\\l\\e\\t\\e\
131
 
 \\-\\-\\e\\x\\c\\l\\u\\d\\e \\*\\.\\p\\y \\a \\b
132
 
    >>> os.system = real_system
133
 
    """
134
 
    cmd = ["rsync", "-av", "--delete"]
 
128
class RsyncUnknownStatus(Exception):
 
129
    def __init__(self, status):
 
130
        Exception.__init__(self, "Unknown status: %d" % status)
 
131
 
 
132
class NoRsync(Exception):
 
133
    def __init__(self, rsync_name):
 
134
        Exception.__init__(self, "%s not found." % rsync_name)
 
135
 
 
136
def rsync(source, target, ssh=False, excludes=(), silent=False, 
 
137
          rsync_name="rsync"):
 
138
    """
 
139
    >>> rsync("a", "b", silent=True)
 
140
    Traceback (most recent call last):
 
141
    RsyncNoFile: No such file a
 
142
    >>> rsync("a", "b", excludes=("*.py",), silent=True)
 
143
    Traceback (most recent call last):
 
144
    RsyncNoFile: No such file a
 
145
    >>> rsync("a", "b", excludes=("*.py",), silent=True, rsync_name="rsyncc")
 
146
    Traceback (most recent call last):
 
147
    NoRsync: rsyncc not found.
 
148
    """
 
149
    cmd = [rsync_name, "-av", "--delete"]
135
150
    if ssh:
136
151
        cmd.extend(('-e', 'ssh'))
137
152
    if len(excludes) > 0:
138
153
        cmd.extend(('--exclude-from', '-'))
139
154
    cmd.extend((source, target))
140
 
    proc = Popen(cmd, stdin=PIPE)
 
155
    if silent:
 
156
        stderr = PIPE
 
157
        stdout = PIPE
 
158
    else:
 
159
        stderr = None
 
160
        stdout = None
 
161
    try:
 
162
        proc = Popen(cmd, stdin=PIPE, stderr=stderr, stdout=stdout)
 
163
    except OSError, e:
 
164
        if e.errno == errno.ENOENT:
 
165
            raise NoRsync(rsync_name)
 
166
            
141
167
    proc.stdin.write('\n'.join(excludes)+'\n')
142
168
    proc.stdin.close()
143
 
    return proc.wait()
144
 
 
145
 
exclusions = ('.bzr/x-push-data', '.bzr/x-pull-data', '.bzr/stat-cache')
146
 
 
147
 
 
148
 
def push(cur_branch, location=None):
 
169
    if silent:
 
170
        proc.stderr.read()
 
171
        proc.stderr.close()
 
172
        proc.stdout.read()
 
173
        proc.stdout.close()
 
174
    proc.wait()
 
175
    if proc.returncode == 12:
 
176
        raise RsyncStreamIO()
 
177
    elif proc.returncode == 23:
 
178
        raise RsyncNoFile(source)
 
179
    elif proc.returncode != 0:
 
180
        raise RsyncUnknownStatus(proc.returncode)
 
181
    return cmd
 
182
 
 
183
 
 
184
def rsync_ls(source, ssh=False, silent=True):
 
185
    cmd = ["rsync"]
 
186
    if ssh:
 
187
        cmd.extend(('-e', 'ssh'))
 
188
    cmd.append(source)
 
189
    if silent:
 
190
        stderr = PIPE
 
191
    else:
 
192
        stderr = None
 
193
    proc = Popen(cmd, stderr=stderr, stdout=PIPE)
 
194
    result = proc.stdout.read()
 
195
    proc.stdout.close()
 
196
    if silent:
 
197
        proc.stderr.read()
 
198
        proc.stderr.close()
 
199
    proc.wait()
 
200
    if proc.returncode == 12:
 
201
        raise RsyncStreamIO()
 
202
    elif proc.returncode == 23:
 
203
        raise RsyncNoFile(source)
 
204
    elif proc.returncode != 0:
 
205
        raise RsyncUnknownStatus(proc.returncode)
 
206
    return [l.split(' ')[-1].rstrip('\n') for l in result.splitlines(True)]
 
207
 
 
208
exclusions = ('.bzr/x-push-data', '.bzr/parent', '.bzr/x-pull-data', 
 
209
              '.bzr/x-pull', '.bzr/pull', '.bzr/stat-cache',
 
210
              '.bzr/x-rsync-data')
 
211
 
 
212
 
 
213
def read_revision_history(fname):
 
214
    return [l.rstrip('\r\n') for l in
 
215
            codecs.open(fname, 'rb', 'utf-8').readlines()]
 
216
 
 
217
class RsyncNoFile(Exception):
 
218
    def __init__(self, path):
 
219
        Exception.__init__(self, "No such file %s" % path)
 
220
 
 
221
class RsyncStreamIO(Exception):
 
222
    def __init__(self):
 
223
        Exception.__init__(self, "Error in rsync protocol data stream.")
 
224
 
 
225
def get_revision_history(location):
 
226
    tempdir = tempfile.mkdtemp('push')
 
227
    try:
 
228
        history_fname = os.path.join(tempdir, 'revision-history')
 
229
        cmd = rsync(location+'.bzr/revision-history', history_fname,
 
230
                    silent=True)
 
231
        history = read_revision_history(history_fname)
 
232
    finally:
 
233
        shutil.rmtree(tempdir)
 
234
    return history
 
235
 
 
236
def history_subset(location, branch):
 
237
    remote_history = get_revision_history(location)
 
238
    local_history = branch.revision_history()
 
239
    if len(remote_history) > len(local_history):
 
240
        return False
 
241
    for local, remote in zip(remote_history, local_history):
 
242
        if local != remote:
 
243
            return False 
 
244
    return True
 
245
 
 
246
def empty_or_absent(location):
 
247
    try:
 
248
        files = rsync_ls(location)
 
249
        return files == ['.']
 
250
    except RsyncNoFile:
 
251
        return True
 
252
 
 
253
def push(cur_branch, location=None, overwrite=False):
149
254
    push_location = get_push_data(cur_branch)
150
255
    if location is not None:
151
256
        if not location.endswith('/'):
162
267
Use "bzr status" to list them."""
163
268
        sys.exit(1)
164
269
    non_source.extend(exclusions)
165
 
 
 
270
    if not overwrite:
 
271
        try:
 
272
            if not history_subset(push_location, cur_branch):
 
273
                raise bzrlib.errors.BzrCommandError("Local branch is not a"
 
274
                                                    " newer version of remote"
 
275
                                                    " branch.")
 
276
        except RsyncNoFile:
 
277
            if not empty_or_absent(push_location):
 
278
                raise bzrlib.errors.BzrCommandError("Remote location is not a"
 
279
                                                    " bzr branch (or empty"
 
280
                                                    " directory)")
 
281
        except RsyncStreamIO:
 
282
            raise bzrlib.errors.BzrCommandError("Rsync could not use the"
 
283
                " specified location.  Please ensure that"
 
284
                ' "%s" is of the form "machine:/path".' % push_location)
166
285
    print "Pushing to %s" % push_location
167
286
    rsync(cur_branch.base+'/', push_location, ssh=True, excludes=non_source)
168
287