1#!/usr/bin/python
2
3"""
4After building can be used to replace documentation,
5and jars with the newly built versions in SVN.
6"""
7
8import filecmp
9import os
10import pipes
11import sys
12
13FILE = 'f'
14DIR = 'd'
15NO_EXIST = 'n'
16
17MIME_TYPES_BY_EXTENSION = {
18  'html': 'text/html;charset=UTF-8',
19  'txt': 'text/plain;charset=UTF-8',
20  'css': 'text/css;charset=UTF-8',
21  'js': 'text/javascript;charset=UTF-8',
22  'jar': 'application/x-java-archive',
23  'xsl': 'text/xml;charset=UTF-8',
24  'gif': 'image/gif',
25  'png': 'image/png'
26  }
27
28def sync(src_to_dest):
29  """
30  Syncrhonize the destination file tree with the source file tree
31  in both the current client and in subversion.
32  """
33
34  def classify(path):
35    if not os.path.exists(path): return NO_EXIST
36    if os.path.isdir(path): return DIR
37    return FILE
38
39  # If we see a case where (conflict) is present, then we need to be
40  # sure to do svn deletes in a separate commit before svn adds.
41  conflict = False
42  # Keep track of changes to make in subversion
43  svn_adds = []
44  svn_deletes = []
45  svn_propsets = {}
46
47  # A bunch of actions that can be taken to synchronize one aspect
48  # of a source file and a destination file
49  def run(argv):
50    """
51    Prints out a command line that needs to be run.
52    """
53    print ' '.join([pipes.quote(arg) for arg in argv])
54
55  def svn(verb_and_flags, args):
56    cmd = ['svn']
57    cmd.extend(verb_and_flags)
58    cmd.extend(args)
59    run(cmd)
60
61  def remove(src, dst): run(['rm', dst])
62
63  def svn_delete(src, dst): svn_deletes.append(dst)
64
65  def recurse(src, dst):
66    children = set()
67    if os.path.isdir(src): children.update(os.listdir(src))
68    if os.path.isdir(dst):
69      children.update(os.listdir(dst))
70    children.discard('.svn')
71    for child in children:
72      handle(os.path.join(src, child), os.path.join(dst, child))
73
74  def copy(src, dst): run(['cp', '-f', src, dst])
75
76  def copy_if_different(src, dst):
77    if not filecmp.cmp(src, dst, shallow=0): copy(src, dst)
78
79  def svn_add(src, dst):
80    svn_adds.append(dst)
81    dot = dst.rfind('.')
82    if dot >= 0:
83      mime_type = MIME_TYPES_BY_EXTENSION.get(dst[dot+1:])
84      if mime_type is not None:
85        key = ('svn:mime-type', mime_type)
86        if key not in svn_propsets:
87          svn_propsets[key] = []
88        svn_propsets[key].append(dst)
89
90  def cnf(src, dst): conflict = True
91
92  def mkdir(src, dst): run(['mkdir', dst])
93
94  # The below table contains the actions to take for each possible
95  # scenario.
96  actions = {
97  # src        dst        actions
98    (NO_EXIST, NO_EXIST): (),
99    (NO_EXIST, FILE)    : (remove, svn_delete,),
100    (NO_EXIST, DIR)     : (recurse, remove, svn_delete,),
101    (FILE,     NO_EXIST): (copy, svn_add,),
102    (FILE,     FILE)    : (copy_if_different,),
103    (FILE,     DIR)     : (recurse, remove, svn_delete, copy, svn_add, cnf),
104    (DIR,      NO_EXIST): (mkdir, svn_add, recurse,),
105    (DIR,      FILE)    : (remove, svn_delete, mkdir, svn_add, recurse, cnf),
106    (DIR,      DIR)     : (recurse,),
107    }
108
109  # Walk the file tree (see recurse action above) and synchronize it at
110  # each step.
111  def handle(src, dst):
112    src_t = classify(src)
113    dst_t = classify(dst)
114    for action in actions[(src_t, dst_t)]: action(src, dst)
115
116  for (src, dst) in src_to_dest:
117    handle(src, dst)
118
119  if len(svn_deletes):
120    svn(['delete'], svn_deletes)
121    if conflict:
122      svn(['commit', '-m', 'remove obsolete files from the snapshot tree'],
123          commit_args)
124  if len(svn_adds):
125    svn(['add', '--depth=empty'], svn_adds)
126  for ((propname, propvalue), files) in svn_propsets.items():
127    svn(['propset', propname, propvalue], files)
128
129if '__main__' == __name__:
130  sync([(sys.argv[1], sys.argv[2])])
131