1"""Bring time stamps of generated checked-in files into the right order 2 3A versioned configuration file .hgtouch specifies generated files, in the 4syntax of make rules. 5 6 output: input1 input2 7 8In addition to the dependency syntax, #-comments are supported. 9""" 10import errno 11import os 12import time 13 14def parse_config(repo): 15 try: 16 fp = repo.wfile(".hgtouch") 17 except IOError, e: 18 if e.errno != errno.ENOENT: 19 raise 20 return {} 21 result = {} 22 with fp: 23 for line in fp: 24 # strip comments 25 line = line.split('#')[0].strip() 26 if ':' not in line: 27 continue 28 outputs, inputs = line.split(':', 1) 29 outputs = outputs.split() 30 inputs = inputs.split() 31 for o in outputs: 32 try: 33 result[o].extend(inputs) 34 except KeyError: 35 result[o] = inputs 36 return result 37 38def check_rule(ui, repo, modified, basedir, output, inputs): 39 """Verify that the output is newer than any of the inputs. 40 Return (status, stamp), where status is True if the update succeeded, 41 and stamp is the newest time stamp assigned to any file (might be in 42 the future). 43 44 If basedir is nonempty, it gives a directory in which the tree is to 45 be checked. 46 """ 47 f_output = repo.wjoin(os.path.join(basedir, output)) 48 try: 49 o_time = os.stat(f_output).st_mtime 50 except OSError: 51 ui.warn("Generated file %s does not exist\n" % output) 52 return False, 0 53 youngest = 0 # youngest dependency 54 backdate = None 55 backdate_source = None 56 for i in inputs: 57 f_i = repo.wjoin(os.path.join(basedir, i)) 58 try: 59 i_time = os.stat(f_i).st_mtime 60 except OSError: 61 ui.warn(".hgtouch input file %s does not exist\n" % i) 62 return False, 0 63 if i in modified: 64 # input is modified. Need to backdate at least to i_time 65 if backdate is None or backdate > i_time: 66 backdate = i_time 67 backdate_source = i 68 continue 69 youngest = max(i_time, youngest) 70 if backdate is not None: 71 ui.warn("Input %s for file %s locally modified\n" % (backdate_source, output)) 72 # set to 1s before oldest modified input 73 backdate -= 1 74 os.utime(f_output, (backdate, backdate)) 75 return False, 0 76 if youngest >= o_time: 77 ui.note("Touching %s\n" % output) 78 youngest += 1 79 os.utime(f_output, (youngest, youngest)) 80 return True, youngest 81 else: 82 # Nothing to update 83 return True, 0 84 85def do_touch(ui, repo, basedir): 86 if basedir: 87 if not os.path.isdir(repo.wjoin(basedir)): 88 ui.warn("Abort: basedir %r does not exist\n" % basedir) 89 return 90 modified = [] 91 else: 92 modified = repo.status()[0] 93 dependencies = parse_config(repo) 94 success = True 95 tstamp = 0 # newest time stamp assigned 96 # try processing all rules in topological order 97 hold_back = {} 98 while dependencies: 99 output, inputs = dependencies.popitem() 100 # check whether any of the inputs is generated 101 for i in inputs: 102 if i in dependencies: 103 hold_back[output] = inputs 104 continue 105 _success, _tstamp = check_rule(ui, repo, modified, basedir, output, inputs) 106 success = success and _success 107 tstamp = max(tstamp, _tstamp) 108 # put back held back rules 109 dependencies.update(hold_back) 110 hold_back = {} 111 now = time.time() 112 if tstamp > now: 113 # wait until real time has passed the newest time stamp, to 114 # avoid having files dated in the future 115 time.sleep(tstamp-now) 116 if hold_back: 117 ui.warn("Cyclic dependency involving %s\n" % (' '.join(hold_back.keys()))) 118 return False 119 return success 120 121def touch(ui, repo, basedir): 122 "touch generated files that are older than their sources after an update." 123 do_touch(ui, repo, basedir) 124 125cmdtable = { 126 "touch": (touch, 127 [('b', 'basedir', '', 'base dir of the tree to apply touching')], 128 "hg touch [-b BASEDIR]") 129} 130