def process_workingcopy(): #command = ['hg', 'status', '--subrepos', '-q', '.'] command = ['hg', 'status', '--subrepos', '-q', '-C', '.'] output = subprocess.Popen( command, stdout=subprocess.PIPE).communicate()[0].decode("utf-8") lines = fixup_renames(output.split('\n')) # decorate entries based on any staging information orphaned_tag = '^' orphaned_count = 0 root = find_hg_root() stage_path = Stage.StageIO().get_staging_root(root, options) if os.path.exists(stage_path): stage_names = os.listdir(stage_path) # if len(stage_names) and len(lines) == 0: # msg = 'ERROR: Orphaned staged entries found in the following areas:\n' # for stage_name in stage_names: # msg += ' [%s]\n' % stage_name # msg += '\nUse "unstage --erase" to clear them.' # self.message = msg # return False for stage_name in stage_names: # reference_count = 0 # capture_count = 0 stage_db_path = os.path.join(stage_path, stage_name) stage_db_file = os.path.join(stage_db_path, 'stage.db') if not os.path.exists(stage_db_file): continue # odd... should probably print a message stage_db = Stage.StageIO().load_stage_db(stage_db_file) # (yes, yes, I know...quadratic complexity: I don't care :) for key in stage_db: staged_entry = stage_db[key] found = False for i in range(len(lines)): if key == lines[i][2:]: # reference_count += 1 if staged_entry.snapshot is None else 0 # capture_count += 1 if staged_entry.snapshot is not None else 0 snap = Stage.StageIO().get_staged_entry_tag( stage_db_path, staged_entry, key) lines[i] = '%s [%s] %s (%s)' % ( lines[i][:1], stage_name, key, snap) found = True break if not found: snap = Stage.StageIO().get_staged_entry_tag( stage_db_path, staged_entry, key) # if this is a refernce, it's orphaned orphaned = '' if staged_entry.snapshot is None: # reference_count += 1 orphaned = orphaned_tag orphaned_count += 1 lines.append('%s [%s] %s%s (%s)' % (staged_entry.state, stage_name, orphaned, key, snap)) self.process_lines(lines, options) if orphaned_count != 0: print( '\n(Use the "staged" command to purge %sorphaned references)' % orphaned_tag) return True
def get_staged_entries(self, options): working_dir = os.getcwd() root = find_hg_root() if root: os.chdir(root) os.chdir("..") if not os.path.exists('.hg'): os.chdir(working_dir) self.message = 'ERROR: Must be in root of working copy to stage.' return [] command = ['hg', 'status', '-q', '-C', '.'] output = subprocess.Popen( command, stdout=subprocess.PIPE).communicate()[0].decode("utf-8") output_lines = fixup_renames(output.split('\n')) stage_name = options.stage_name self.stage_path = super(Staged, self).get_staging_root(root, options) if not os.path.exists(self.stage_path): os.mkdir(self.stage_path) stage_names = [] if stage_name is None: # gather up all staging area names for entry in os.listdir(self.stage_path): stage_names.append(entry) else: stage_names.append(stage_name) staged_entries = {} for stage_name in stage_names: stage_db_path = os.path.join(self.stage_path, stage_name) stage_db_file = os.path.join(stage_db_path, 'stage.db') if not os.path.exists(stage_db_file): continue # odd... should probably print a message stage_db = super(Staged, self).load_stage_db(stage_db_file) reference_count = 0 capture_count = 0 entries = [] bad_keys = [] for key in stage_db: staged_entry = stage_db[key] reference_count += 1 if staged_entry.snapshot is None else 0 capture_count += 1 if staged_entry.snapshot is not None else 0 found = False for line in output_lines: if (key in line) or (staged_entry.snapshot is not None): snapshot_path = None snap = super(Staged, self).get_staged_entry_tag( stage_db_path, staged_entry, key) entries.append('%s (%s)' % (line, snap)) found = True break if not found: if staged_entry.snapshot is None: bad_keys.append(key) else: # note: snapshots are independent of the state of their source files snap = super(Staged, self).get_staged_entry_tag( stage_db_path, staged_entry, key) entries.append('%s %s (%s)' % (staged_entry.state, key, snap)) if len(bad_keys): # all 'bad_keys' are references for key in bad_keys: del stage_db[key] if len(stage_db): # save the corrected database super(Staged, self).save_stage_db(stage_db, stage_db_file) staged_entries[stage_name] = entries else: if (len(output_lines) == 0) and (reference_count != 0) and (capture_count == 0): print('WARNING: Purging orphaned staging area "%s".' % stage_name) # if (len(output_lines) == 0) and (reference_count != 0) and (capture_count == 0): # # orphaned references found # os.chdir(working_dir) # msg = 'ERROR: Orphaned reference entries found in the following staging areas:\n' # for stage_name in stage_names: # msg += ' [%s]\n' % stage_name # msg += '\nUse "unstage --erase" to clear them.' # self.message = msg # return [] if os.path.exists(stage_db_path): shutil.rmtree(stage_db_path) os.chdir(working_dir) return staged_entries
def __init__(self, options): super(Unstage, self).__init__() if not options.branch: print('ERROR: Could not determine branch.', file=sys.stderr) sys.exit(1) root = find_hg_root() if root: os.chdir(root) os.chdir("..") if not os.path.exists('.hg'): os.chdir(working_dir) print('ERROR: Must be in root of working copy to stage.', file=sys.stderr) sys.exit(1) stage_name = "default" if options.stage_name is None else options.stage_name stage_path = super(Unstage, self).get_staging_root(root, options) if not os.path.exists(stage_path): os.mkdir(stage_path) stage_db_path = os.path.join(stage_path, stage_name) stage_db_file = os.path.join(stage_db_path, 'stage.db') if not os.path.exists(stage_db_path): if options.erase_cache: return # nothing more to do print( 'ERROR: No modifications are currently staged in "%s" for committing.' % stage_name, file=sys.stderr) sys.exit(1) if options.erase_cache: print('All entries in the "%s" staging area were cleared.' % stage_name) shutil.rmtree(stage_db_path) return if len(options.args) == 0: print('ERROR: No filter(s) specified for unstaging.', file=sys.stderr) sys.exit(1) stage_db = {} if os.path.exists(stage_db_file): stage_db = super(Unstage, self).load_stage_db(stage_db_file) command = ['hg', 'status', '-q', '-C', '.'] output = subprocess.Popen( command, stdout=subprocess.PIPE).communicate()[0].decode("utf-8") output_lines = fixup_renames(output.split('\n')) bad_keys = [] for key in stage_db: for item in options.args: if item in key: bad_keys.append(key) if len(bad_keys) == 0: print( 'ERROR: Your specified filter(s) did not match any entries in the "%s" staging area.' % stage_name, file=sys.stderr) sys.exit(1) unstaged_entries = [] for key in bad_keys: for line in output_lines: if key in line: unstaged_entries.append(line) for key in bad_keys: if stage_db[key].snapshot is not None: # it's a snapshot, delete it as well snapshot_file = os.path.join(stage_db_path, stage_db[key].snapshot) os.remove(snapshot_file) del stage_db[key] if len(stage_db) == 0: if os.path.exists(stage_db_path): shutil.rmtree(stage_db_path) else: # save the new database super(Unstage, self).save_stage_db(stage_db, stage_db_file) if len(unstaged_entries): print( 'The following existing entries were removed from the "%s" staging area:' % stage_name) s = Info.Status() s.process_lines(unstaged_entries, options) else: print( 'No unique entries were removed from the "%s" staging area.' % stage_name)
def __init__(self, options): super(Stage, self).__init__() def generate_snapshot(options, stage_db_path, file_path, entry): if (entry is None) or (entry.snapshot is None): if (not options.snapshot) or (not os.path.exists(file_path)): return StageEntry(None, entry.state) if entry.state != 'M': print( "ERROR: Only (M)odified files can be captured by snapshot.", file=sys.stderr) sys.exit(1) ss = entry.snapshot if ss is None: ss = str(uuid.uuid4()).replace('-', '') snapshot_file_name = os.path.join(stage_db_path, ss) if os.path.exists(snapshot_file_name): os.remove(snapshot_file_name) # we are refreshing the snapshot if not os.path.exists(stage_db_path): os.mkdir(stage_db_path) # use copy2() to make sure the snapshot shares the timestamp of # the source file at the time of creation shutil.copy2(file_path, snapshot_file_name) return StageEntry(ss, entry.state) if not options.branch: print('ERROR: Could not determine branch.', file=sys.stderr) sys.exit(1) root = find_hg_root() if root: os.chdir(root) os.chdir("..") if not os.path.exists('.hg'): os.chdir(working_dir) print('ERROR: Must be in root of working copy to stage.', file=sys.stderr) sys.exit(1) stage_name = "default" if options.stage_name is None else options.stage_name stage_db = {} stage_path = super(Stage, self).get_staging_root(root, options) if not os.path.exists(stage_path): os.mkdir(stage_path) stage_db_path = os.path.join(stage_path, stage_name) stage_db_file = os.path.join(stage_db_path, 'stage.db') if os.path.exists(stage_db_file): if options.erase_cache: print('All staged entries in "%s" cleared.' % stage_name) shutil.rmtree(stage_db_path) else: stage_db = super(Stage, self).load_stage_db(stage_db_file) if options.erase_cache: return # nothing else to do if (len(options.args) == 0): capture_count = 0 for key in stage_db: if stage_db[key].snapshot is not None: capture_count += 1 if capture_count: try: print( 'You are about to refresh snapshot entries in the "%s" staging area.' % stage_name) input( 'Press ENTER if this is the intent (or press Ctrl-C to abort):' ) except SyntaxError: pass command = ['hg', 'status', '-q', '-C', '.'] output = subprocess.Popen( command, stdout=subprocess.PIPE).communicate()[0].decode("utf-8") output_lines = fixup_renames(output.split('\n')) lines = [] if len(options.args): newlines = [] # they are specifying files to be staged...filter 'lines' # based on them for item in options.args: found = -1 for i in range(len(output_lines)): if item in output_lines[i]: newlines.append(output_lines[i][2:]) break if len(newlines) == 0: print( "ERROR: Your specified filter(s) did not match any pending changes in the working copy.", file=sys.stderr) sys.exit(1) lines = newlines else: # strip off the status bit for i in range(len(output_lines)): lines.append(output_lines[i][2:]) if len(lines) == 0: print("ERROR: No files have been selected for staging.", file=sys.stderr) sys.exit(1) # filter out duplicate entries status_db = {} for path in output_lines: key = path[2:].strip() status_db[key] = StageEntry(None, path[:1]) added_files = [] refreshed_files = [] # all the files in lines[] are to be added to the staging database for path in lines: if path in stage_db: stage_db[path] = generate_snapshot(options, stage_db_path, path, stage_db[path]) refreshed_files.append('%s %s' % (stage_db[path].state, path)) else: for l in output_lines: if path in l: added_files.append(l) break entry = StageEntry() if path in status_db: entry = status_db[path] stage_db[path] = generate_snapshot(options, stage_db_path, path, entry) bad_keys = [] for key in stage_db: if key not in status_db: # this file had been staged, but is now no longer modified bad_keys.append(key) for key in bad_keys: if stage_db[key].snapshot is not None: # it's a snapshot, delete it as well snapshot_file = os.path.join(stage_db_path, stage_db[key].snapshot) os.remove(snapshot_file) del status_db[key] # save the new database super(Stage, self).save_stage_db(stage_db, stage_db_file) if len(added_files) or len(refreshed_files): s = Info.Status() if len(added_files): print( 'The following new %s entries were added to the "%s" staging area:' % ('snapshot' if options.snapshot else 'reference', stage_name)) s.process_lines(added_files, options) if len(refreshed_files): print( 'The following snapshot entries were refreshed in the "%s" staging area:' % stage_name) s.process_lines(refreshed_files, options) else: print('No unique entries were added to the "%s" staging area.' % stage_name)
def execute(self, options, quiet=False, **kwargs): if not options.branch: self.message = 'ERROR: Could not determine branch.' return False working_dir = os.getcwd() root = find_hg_root() if root: os.chdir(root) os.chdir("..") stage_path = StageIO().get_staging_root(root, options) if os.path.exists(stage_path): stages = os.listdir(stage_path) stage_path = os.path.join(".hg", "stage") if len(stages) != 0 else None else: stage_path = None if not os.path.exists('.hg'): os.chdir(working_dir) self.message = 'ERROR: Must be in root of working copy to shelf.' return False command = ['hg', 'status', '-q', '-C'] if (options.include_filter is None) and (len(options.exclude_filter) == 0): command.append(options.use_path) output = subprocess.Popen( command, stdout=subprocess.PIPE).communicate()[0].decode("utf-8") if len(output) > 0: lines = fixup_renames(output.split('\n')) shelf_name = 'shelf' if len(options.shelf_name) != 0: shelf_name = options.shelf_name shelf_name_unquoted = shelf_name shelf_name = quote(shelf_name, '') if 'mb_root' in kwargs: root = kwargs['mb_root'] else: root = find_mb_root( ) # this will not return if we can't find a working location manifest_version = 0 manifest = [] manifest_name = os.path.join(root, '%s.manifest' % shelf_name) manifest_archive = os.path.join(root, '%s.7z' % shelf_name) manifest_comment = '' timestamp = hex(int(time.time()))[2:] if os.path.exists(manifest_name): # grab the previous comment manifest_lines = open(manifest_name).readlines() if manifest_lines[0].startswith('version '): manifest_version = int(manifest_lines[0][8:]) if manifest_version >= 1: manifest_comment = manifest_lines[1].rstrip() else: manifest_comment = manifest_lines[0].rstrip() manifest_lines = None try: os.rename(manifest_name, '%s.%s' % (manifest_name, timestamp)) except: os.chdir(working_dir) self.message = 'ERROR: Could not back up previous shelf.' return False if os.path.exists(manifest_archive): try: os.rename(manifest_archive, '%s.%s' % (manifest_archive, timestamp)) except: os.chdir(working_dir) self.message = 'ERROR: Could not back up previous shelf.' return False if len(options.comment): manifest_comment = options.comment shelve_command = [ options.seven_zip, 'a', manifest_archive, '@%s.list' % shelf_name ] for line in lines: line = line.strip() if not len(line): continue if (options.include_filter is not None) or len( options.exclude_filter): if options.include_filter is not None: if options.include_filter in line: manifest.append(line) if len(options.exclude_filter): exclude = [ f for f in options.exclude_filter if f in line ] if len(exclude) == 0: manifest.append(line) else: manifest.append(line) if options.ide_state: # find all the .suo files and add them to the archive # (the Visual Studio .suo file maintains a record of all files that # were last open in the IDE) if os.path.exists('.vs'): # VS2017+ for root, dirs, files in os.walk('.vs'): if '.suo' in files: manifest.append('X %s' % os.path.join(root, '.suo')) options.extra_files.append( os.path.join(root, '.suo')) else: # +VS2013 files = glob.glob('*.suo') if len(files): options.extra_files += files for file in files: manifest.append('X %s' % file) lines_written = 0 with open('%s.list' % shelf_name, 'w') as f: if stage_path is not None: f.write('%s\n' % stage_path) # capture current staging metadata if isinstance(options.extra_files, (list, tuple)) and len(options.extra_files): # should be a path relative to the root of the working copy f.write('%s\n' % '\n'.join(options.extra_files)) for line in manifest: action = line[0] if action == 'M' or action == 'A': f.write('%s\n' % line[2:]) lines_written += 1 if action == 'V': # rename; place the current file into the backup in case it holds changes f.write('%s\n' % line[2:].split(',')[1]) lines_written += 1 if lines_written: output = subprocess.Popen( shelve_command, stdout=subprocess.PIPE).communicate()[0].decode("utf-8") lines = output.split('\n') something_went_wrong = True for line in lines: line = line.rstrip() if line == 'Everything is Ok': something_went_wrong = False break if something_went_wrong: os.chdir(working_dir) self.message = 'ERROR: Failed to construct shelf archive:\n%s' % output return False os.remove('%s.list' % shelf_name) if (options.include_filter is not None) or len( options.exclude_filter): for line in manifest: action = line[0] filename = line[2:] if action == 'V': filename = line[2:].split(',')[1] command = ['hg', 'revert', filename] output = subprocess.Popen( command, stdout=subprocess.PIPE).communicate()[0].decode( "utf-8") elif not options.no_revert: command = ['hg', 'revert', '--all'] if options.use_path != '.': command.append(options.use_path) output = subprocess.Popen( command, stdout=subprocess.PIPE).communicate()[0].decode("utf-8") if stage_path is not None: shutil.rmtree( stage_path) # remove current staging metadata with open(manifest_name, 'w') as f: f.write('version %d\n' % MANIFEST_VERSION) f.write('%s\n' % manifest_comment) for line in manifest: action = line[0] if os.name == 'nt': file_name = line[2:].replace('/', '\\') else: file_name = line[2:].replace('\\', '/') #timestamp = 0.0 changeset = '' if action == 'M': changeset = hashlib.md5(open(file_name, 'rb').read()).hexdigest() elif action == 'V': # the revert above may have left the renamed file in place # we are nice, and clean it up for them... from_name, to_name = file_name.split(',') if os.path.exists(to_name): try: os.remove(to_name) except: self.message = 'ERROR: Failed to remove renamed file "%s"!' % to_name return False changeset = hashlib.md5(open(from_name, 'rb').read()).hexdigest() f.write('%s?%s?%s\n' % (action, file_name, changeset)) batch_text = '' if os.name == 'nt': batch_text = '@echo off\n' batch_text += 'set FG=%_fg\n' batch_text += 'set BG=%_bg\n' manifest = colorize_status(manifest) if not quiet: print('Shelved the following state as microbranch "%s":' % shelf_name_unquoted) for line in manifest: if options.ansi_color: if options.ansi_color_requires_batch: batch_text += 'echo %s\n' % line[3] else: print(line[3]) else: print(line[0], line[1]) if options.ansi_color: if options.ansi_color_requires_batch: if os.name == 'nt': batch_text += 'color %FG on %BG\n' open(options.batch_file_name, 'w').write(batch_text) os.system(options.batch_file_name) if options.no_revert: print( '\nAs requested, changes have been left in the working copy.' ) else: if not quiet: print('Nothing to shelve.') os.chdir(working_dir) return True
def __init__(self, options): if not options.branch: return command = ['hg', 'log', '-v', '--removed'] if len(options.log_rev) != 0: command += ['-r', options.log_rev] elif len(options.log_user): command += ['-u', options.log_user] elif len(options.log_date): command += ['-d', options.log_date] elif len(options.log_keyword): command += ['-k', options.log_keyword] if options.log_limit != 0: command += ['-l', options.log_limit] if len(options.log_rev) == 0: if len(options.log_branch): command += ['-b', options.log_branch] else: command += ['-b', options.branch] if options.log_no_merges: command.append('-M') if options.detailed: command.append('--debug') if len(options.log_template): command += ['-T', options.log_template] output = subprocess.Popen( command, stdout=subprocess.PIPE).communicate()[0].decode("utf-8") print(output) else: output = subprocess.Popen( command, stdout=subprocess.PIPE).communicate()[0].decode("utf-8") if len(output) == 0: print("ERROR: Invalid revision provided", file=sys.stderr) sys.exit(1) lines = output.split('\n') blank_count = 0 has_file_changes = False id = '' i = 0 while i < len(lines): line = lines[i] if line.startswith('changeset: '): if len(id) != 0: print('-' * 40) id = line.split(' ')[-1] if ':' in id: id = id.split(':')[1] print('%s%s%s' % (Colors['BrightGreen'], line, Colors['Reset'])) has_file_changes = False elif line.startswith('description:'): print(line) blank_count = 0 i += 1 while i < len(lines): if len(lines[i].strip()) == 0: blank_count += 1 else: if blank_count: print('\n' * blank_count) blank_count = 0 print('%s%s%s' % (Colors['BrightYellow'], lines[i], Colors['Reset'])) i += 1 try: if (lines[i].startswith('changeset: ')): i -= 1 break except IndexError: pass # we've exceeded the array bounds if has_file_changes: print('changes:') command = ['hg', 'status', '-C', '--change', id] output = subprocess.Popen( command, stdout=subprocess.PIPE).communicate()[0].decode( "utf-8") if len(output) == 0: print("ERROR: Invalid revision provided", file=sys.stderr) sys.exit(1) change_lines = fixup_renames(output.split('\n')) Status().process_lines(change_lines, options) print(Colors['Reset']) elif line.startswith('files:'): has_file_changes = True else: print(line) i += 1