def system_date(datestring): m = re.match(r"^(.+)([+-]\d\d:?\d\d)$", datestring) if m: # Time zone included; we parse it ourselves, since "date" # would convert it to the local time zone. (ds, z) = m.groups() try: t = Run("date", "+%Y-%m-%d-%H-%M-%S", "-d", ds).output_one_line() except RunException: return None else: # Time zone not included; we ask "date" to provide it for us. try: d = Run("date", "+%Y-%m-%d-%H-%M-%S_%z", "-d", datestring).output_one_line() except RunException: return None (t, z) = d.split("_") year, month, day, hour, minute, second = [int(x) for x in t.split("-")] try: return datetime(year, month, day, hour, minute, second, tzinfo=TimeZone(z)) except ValueError: raise DateException(datestring, "date")
def __import_mail_path(mail_path, filename, options): with open(mail_path, 'rb') as f: mail = f.read() msg_path = mail_path + '-msg' patch_path = mail_path + '-patch' mailinfo_lines = Run( 'git', 'mailinfo', msg_path, patch_path ).encoding(None).decoding(None).raw_input(mail).output_lines(b'\n') mailinfo = dict(line.split(b': ', 1) for line in mailinfo_lines if line) with open(msg_path, 'rb') as f: msg_body = f.read() msg_bytes = mailinfo[b'Subject'] + b'\n\n' + msg_body with open(patch_path, 'rb') as f: diff = f.read() __create_patch( None if options.mbox else filename, decode_utf8_with_latin1(msg_bytes), mailinfo[b'Author'].decode('utf-8'), mailinfo[b'Email'].decode('utf-8'), mailinfo[b'Date'].decode('utf-8'), diff, options, )
def add_trailer(message, trailer, name, email): trailer_line = '%s: %s <%s>' % (trailer, name, email) return ( Run('git', 'interpret-trailers', '--trailer', trailer_line) .raw_input(message) .raw_output() )
def remove_section(self, name): """Remove a section in the config file. Silently do nothing if the section doesn't exist.""" Run('git', 'config', '--remove-section', name).returns( [0, 1, 128] ).discard_stderr().discard_output() self._cache.clear()
def __import_mail_path(mail_path, filename, options): with open(mail_path, 'rb') as f: mail = f.read() msg_path = mail_path + '-msg' patch_path = mail_path + '-patch' mailinfo_cmd = ['git', 'mailinfo'] if options.message_id or config.getbool('stgit.import.message-id'): mailinfo_cmd.append('--message-id') mailinfo_cmd.extend([msg_path, patch_path]) mailinfo_lines = (Run(*mailinfo_cmd).encoding(None).decoding( None).raw_input(mail).output_lines(b'\n')) mailinfo = dict(line.split(b': ', 1) for line in mailinfo_lines if line) with open(msg_path, 'rb') as f: msg_body = f.read() msg_bytes = mailinfo[b'Subject'] + b'\n\n' + msg_body with open(patch_path, 'rb') as f: diff = f.read() __create_patch( None if options.mbox else filename, decode_utf8_with_latin1(msg_bytes), None, mailinfo[b'Author'].decode('utf-8'), mailinfo[b'Email'].decode('utf-8'), mailinfo[b'Date'].decode('utf-8'), diff, options, )
def func(parser, options, args): if options.clear: if options.diff or options.number or options.full or options.graphical: parser.error('cannot combine --clear with other options') elif args: parser.error('cannot combine --clear with patch arguments') if options.graphical: for o in ['diff', 'number', 'full']: if getattr(options, o): parser.error('cannot combine --graphical and --%s' % o) stack = directory.repository.get_stack(options.branch) patches = parse_patches(args, list(stack.patchorder.all)) logref = log.log_ref(stack.name) try: logcommit = stack.repository.refs.get(logref) except KeyError: out.info('Log is empty') return if options.clear: log.delete_log(stack.repository, stack.name) return stacklog = log.get_log_entry(stack.repository, logref, logcommit) pathlim = [os.path.join('patches', pn) for pn in patches] if options.graphical: cmd = ['gitk', stacklog.simplified.sha1, '--'] + pathlim # Discard the exit codes generated by SIGINT, SIGKILL, and SIGTERM. Run(*cmd).returns([0, -2, -9, -15]).run() else: show_log(stacklog.simplified, pathlim, options.number, options.full, options.diff)
def _get_git_dir(self): try: return Run( 'git', 'rev-parse', '--git-dir' ).discard_stderr().output_one_line() except RunException: return None
def func(parser, options, args): """Show commit log and diff """ if options.applied: patches = crt_series.get_applied() elif options.unapplied: patches = crt_series.get_unapplied() elif len(args) == 0: patches = ['HEAD'] elif '..' in ' '.join(args): # patch ranges applied = crt_series.get_applied() unapplied = crt_series.get_unapplied() patches = parse_patches(args, applied + unapplied + \ crt_series.get_hidden(), len(applied)) else: # individual patches or commit ids patches = args if not options.stat: options.diff_flags.extend(color_diff_flags()) commit_ids = [git_id(crt_series, patch) for patch in patches] commit_bytes = b'\n'.join( (Run('git', 'show', *(options.diff_flags + [commit_id])).decoding(None).raw_output()) for commit_id in commit_ids) if options.stat: commit_bytes = git.diffstat(commit_bytes).encode('utf-8') if commit_bytes: pager(commit_bytes)
def rename_section(self, from_name, to_name): """Rename a section in the config file. Silently do nothing if the section doesn't exist.""" Run('git', 'config', '--rename-section', from_name, to_name).returns( [0, 1, 128] ).run() self._cache.clear()
def git_describe_version(): path = sys.path[0] try: v = Run('git', 'describe', '--tags', '--abbrev=4' ).cwd(path).output_one_line() except RunException as e: raise VersionUnavailable(str(e)) if not re.match(r'^v[0-9]', v): raise VersionUnavailable('%s: bad version' % v) try: dirty = Run('git', 'diff-index', '--name-only', 'HEAD' ).cwd(path).raw_output() except RunException as e: raise VersionUnavailable(str(e)) if dirty: v += '-dirty' return utils.strip_prefix('v', v)
def run(self, args, env={}): """Run the given command with an environment given by self.env. @type args: list of strings @param args: Command and argument vector @type env: dict @param env: Extra environment""" return Run(*args).env(utils.add_dict(self.env, env))
def __check_git_version(): """Check the minimum GIT version """ from stgit.run import Run gitver = Run('git', '--version').output_one_line().split()[2] if not __check_min_version(version.git_min_ver, gitver): print('GIT version %s or newer required. Found %s' % (version.git_min_ver, gitver), file=sys.stderr) sys.exit(1)
def diffstat(diff): """Return the diffstat of the supplied diff.""" return ( Run('git', 'apply', '--stat', '--summary') .encoding(None) .raw_input(diff) .decoding('utf-8') .raw_output() )
def __mailsplit(tmpdir, filename, options): mailsplit_cmd = ['git', 'mailsplit', '-d4', '-o' + tmpdir] if options.mail: mailsplit_cmd.append('-b') if options.keep_cr: mailsplit_cmd.append('--keep-cr') if filename: mailsplit_cmd.extend(['--', filename]) r = Run(*mailsplit_cmd) else: stdin = os.fdopen(sys.__stdin__.fileno(), 'rb') r = Run(*mailsplit_cmd).encoding(None).raw_input(stdin.read()) num_patches = int(r.output_one_line()) mail_paths = [os.path.join(tmpdir, '%04d' % n) for n in range(1, num_patches + 1)] return mail_paths
def show_log(stacklog, pathlim, num, full, show_diff): cmd = ['git', 'log'] if num is not None and num > 0: cmd.append('-%d' % num) if show_diff: cmd.append('-p') elif not full: cmd.append('--pretty=tformat:%h %aD %s') cmd.extend([stacklog.sha1, '--']) cmd.extend(pathlim) Run(*cmd).run()
def load(self): """Load the whole configuration in __cache unless it has been done already.""" if self.__cache is not None: return self.__cache = self.__defaults lines = Run('git', 'config', '--null', '--list').discard_exitcode().output_lines('\0') for line in lines: key, value = line.split('\n', 1) self.__cache.setdefault(key, []).append(value)
def git_describe_version(): root = sys.path[0] if sys.path[0] else None try: v = (Run('git', 'describe', '--tags', '--abbrev=4').cwd(root).discard_stderr().output_one_line()) except RunException as e: raise VersionUnavailable(str(e)) m = re.match(r'^v([0-9].*)', v) if m: v = m.group(1) else: raise VersionUnavailable('bad version: %s' % v) try: dirty = (Run('git', 'diff-index', '--name-only', 'HEAD').cwd(root).discard_stderr().raw_output()) except RunException as e: raise VersionUnavailable(str(e)) if dirty: v += '-dirty' return v
def default_worktree(self): """A L{Worktree} object representing the default work tree.""" if self._default_worktree is None: path = environ_get('GIT_WORK_TREE', None) if not path: o = Run('git', 'rev-parse', '--show-cdup').output_lines() o = o or ['.'] assert len(o) == 1 path = o[0] self._default_worktree = Worktree(path) return self._default_worktree
def __topdir_path(self): try: lines = Run('git', 'rev-parse', '--show-cdup' ).discard_stderr().output_lines() if len(lines) == 0: return '.' elif len(lines) == 1: return lines[0] else: raise RunException('Too much output') except RunException: raise DirectoryException('No git repository found')
def add_trailers(message, trailers, name, email): trailer_args = [] for trailer, user_value in trailers: if user_value is None: trailer_args.extend( ['--trailer', '%s: %s <%s>' % (trailer, name, email)]) else: trailer_args.extend( ['--trailer', '%s: %s' % (trailer, user_value)]) return (Run('git', 'interpret-trailers', *trailer_args).raw_input(message).raw_output())
def sections_matching(self, regexp): """Takes a regexp with a single group, matches it against all config variables, and returns a list whose members are the group contents, for all variable names matching the regexp. """ result = [] for line in Run('git', 'config', '--get-regexp', '"^%s$"' % regexp ).returns([0, 1]).output_lines(): m = re.match('^%s ' % regexp, line) if m: result.append(m.group(1)) return result
def read_commit_dag(branch): out.start('Reading commit DAG') commits = {} patches = set() for line in Run('git', 'rev-list', '--parents', '--all').output_lines(): cs = line.split() for id in cs: if id not in commits: commits[id] = Commit(id) for id in cs[1:]: commits[cs[0]].parents.add(commits[id]) commits[id].children.add(commits[cs[0]]) for line in Run('git', 'show-ref').output_lines(): id, ref = line.split() m = re.match(r'^refs/patches/%s/(.+)$' % re.escape(branch), ref) if m and not m.group(1).endswith('.log'): c = commits[id] c.patch = m.group(1) patches.add(c) out.done() return commits, patches
def func(parser, options, args): """Show commit log and diff""" if args and (options.applied or options.unapplied): parser.error('patches may not be given with --applied or --unapplied') elif options.applied and options.unapplied: parser.error('cannot use both --applied and --unapplied') repository = directory.repository stack = repository.get_stack(options.branch) patchorder = stack.patchorder if options.applied: commits = [stack.patches.get(pn).commit for pn in patchorder.applied] elif options.unapplied: commits = [stack.patches.get(pn).commit for pn in patchorder.unapplied] elif not args: commits = [stack.top] elif '..' in ' '.join(args): # patch ranges patch_names = parse_patches( args, patchorder.all, len(patchorder.applied), ) commits = [stack.patches.get(pn).commit for pn in patch_names] else: commits = [] for name in args: if stack.patches.exists(name): commits.append(stack.patches.get(name).commit) else: try: commits.append( repository.rev_parse( name, object_type='commit', discard_stderr=True ) ) except RepositoryException: raise RepositoryException( '%s: Unknown patch or revision name' % name ) cmd = ['git', 'show'] if options.stat: cmd.extend(['--stat', '--summary']) else: cmd.append('--patch') cmd.extend(options.diff_flags) cmd.extend(color_diff_flags()) cmd.extend(commit.sha1 for commit in commits) pager(Run(*cmd).decoding(None).raw_output())
def git_date(datestring=''): try: ident = (Run('git', 'var', 'GIT_AUTHOR_IDENT').env({ 'GIT_AUTHOR_NAME': 'XXX', 'GIT_AUTHOR_EMAIL': 'XXX', 'GIT_AUTHOR_DATE': datestring, }).output_one_line()) except RunException: return None _, _, timestamp, offset = ident.split() return datetime.fromtimestamp(int(timestamp), TimeZone(offset))
def load(self): """Load the configuration in _cache unless it has been done already.""" if self._cache is not None: return self._cache = DEFAULTS.copy() lines = Run('git', 'config', '--null', '--list' ).discard_exitcode().output_lines('\0') for line in lines: try: key, value = line.split('\n', 1) except ValueError: key = line value = None self._cache.setdefault(key, []).append(value)
def pull(repository='origin', refspec=None): """Fetches changes from the remote repository, using 'git pull' by default. """ # we update the HEAD __clear_head_cache() args = [repository] if refspec: args.append(refspec) command = (config.get('branch.%s.stgit.pullcmd' % get_head_file()) or config.get('stgit.pullcmd')) Run(*(command.split() + args)).run()
def get(): """Return the .git directory location """ global __base_dir if not __base_dir: if 'GIT_DIR' in os.environ: __base_dir = os.environ['GIT_DIR'] else: try: __base_dir = Run('git', 'rev-parse', '--git-dir').output_one_line() except RunException: __base_dir = '' return __base_dir
def get(): """Return the .git directory location """ global __base_dir if not __base_dir: __base_dir = environ_get('GIT_DIR') if __base_dir is None: try: __base_dir = Run('git', 'rev-parse', '--git-dir').output_one_line() except RunException: __base_dir = '' return __base_dir
def get_template(tfile): """Return the string in the template file passed as argument or None if the file wasn't found. """ tmpl_dirs = [ Run('git', 'rev-parse', '--git-dir').output_one_line(), os.path.join(os.path.expanduser('~'), '.stgit', 'templates'), os.path.join(sys.prefix, 'share', 'stgit', 'templates'), os.path.join(os.path.dirname(__file__), 'templates'), ] for d in tmpl_dirs: tmpl_path = os.path.join(d, tfile) if os.path.isfile(tmpl_path): with io.open(tmpl_path, 'r') as f: return f.read() else: return None
def rebase(tree_id=None): """Rebase the current tree to the give tree_id. The tree_id argument may be something other than a GIT id if an external command is invoked. """ command = (config.get('branch.%s.stgit.rebasecmd' % get_head_file()) or config.get('stgit.rebasecmd')) if tree_id: args = [tree_id] elif command: args = [] else: raise GitException('Default rebasing requires a commit id') if command: # clear the HEAD cache as the custom rebase command will update it __clear_head_cache() Run(*(command.split() + args)).run() else: # default rebasing reset(tree_id)