def __send_message(msg_type, tmpl, options, *args): """Message sending dispatcher. """ domain = options.domain or config.get('stgit.domain') if domain: if sys.version_info < (3, 2): raise CmdException("Setting domain requires Python version 3.2+") msg_id = email.utils.make_msgid('stgit', domain=domain) else: msg_id = email.utils.make_msgid('stgit') if msg_type == 'cover': assert len(args) == 1, 'too many args for msg_type == "cover"' patches = args[0] msg = __build_cover(tmpl, msg_id, options, patches) outstr = 'the cover message' elif msg_type == 'patch': patch, patch_nr, total_nr, ref_id = args msg = __build_message(tmpl, msg_id, options, patch, patch_nr, total_nr, ref_id) outstr = 'patch "%s"' % patch else: raise AssertionError('invalid msg_type: %s' % msg_type) # pragma: no cover if hasattr(msg, 'as_bytes'): msg_bytes = msg.as_bytes(options.mbox) else: msg_bytes = msg.as_string(options.mbox) # Python 3.3 only has Message.as_string(). We encode it back to bytes # and hope for the best. if isinstance(msg_bytes, text): msg_bytes = msg_bytes.encode('utf-8') if options.mbox: out.stdout_bytes(msg_bytes + b'\n') return msg_id if not options.git: from_addr, to_addrs = __parse_addresses(msg) out.start('Sending ' + outstr) smtpserver = options.smtp_server or config.get('stgit.smtpserver') if options.git: __send_message_git(msg_bytes, msg['From'], options) elif smtpserver.startswith('/'): # Use the sendmail tool __send_message_sendmail(smtpserver, msg_bytes) else: # Use the SMTP server (we have host and port information) __send_message_smtp(smtpserver, from_addr, to_addrs, msg_bytes, options) # give recipients a chance of receiving related patches in correct order if msg_type == 'cover' or (msg_type == 'patch' and patch_nr < total_nr): sleep = options.sleep or config.getint('stgit.smtpdelay') time.sleep(sleep) if not options.git: out.done() return msg_id
def get_format_version(): """Return the integer format version number, or None if the branch doesn't have any StGit metadata at all, of any version.""" fv = config.get(key) ofv = config.get(old_key) if fv: # Great, there's an explicitly recorded format version # number, which means that the branch is initialized and # of that exact version. return int(fv) elif ofv: # Old name for the version info: upgrade it. config.set(key, ofv) config.unset(old_key) return int(ofv) elif os.path.isdir(os.path.join(branch_dir, 'patches')): # There's a .git/patches/<branch>/patches dirctory, which # means this is an initialized version 1 branch. return 1 elif os.path.isdir(branch_dir): # There's a .git/patches/<branch> directory, which means # this is an initialized version 0 branch. return 0 else: # The branch doesn't seem to be initialized at all. return None
def fetch(repository, remote_repository): """Fetch changes from remote repository using 'git fetch' by default""" command = (config.get( 'branch.%s.stgit.fetchcmd' % repository.current_branch_name) or config.get('stgit.fetchcmd')).split() command.append(remote_repository) repository.run(command).run()
def get_format_version(): """Return the integer format version number, or None if the branch doesn't have any StGIT metadata at all, of any version.""" fv = config.get(self.format_version_key()) ofv = config.get('branch.%s.stgitformatversion' % self.get_name()) if fv: # Great, there's an explicitly recorded format version # number, which means that the branch is initialized and # of that exact version. return int(fv) elif ofv: # Old name for the version info, upgrade it config.set(self.format_version_key(), ofv) config.unset('branch.%s.stgitformatversion' % self.get_name()) return int(ofv) elif os.path.isdir(os.path.join(branch_dir, 'patches')): # There's a .git/patches/<branch>/patches dirctory, which # means this is an initialized version 1 branch. return 1 elif os.path.isdir(branch_dir): # There's a .git/patches/<branch> directory, which means # this is an initialized version 0 branch. return 0 else: # The branch doesn't seem to be initialized at all. return None
def pull(repository, remote_repository): """Pull changes from remote repository using 'git pull' by default.""" command = (config.get( 'branch.%s.stgit.pullcmd' % repository.current_branch_name) or config.get('stgit.pullcmd')).split() command.append(remote_repository) repository.run(command).run() repository.refs.reset_cache()
def get_editor(): for editor in [os.environ.get('GIT_EDITOR'), config.get('stgit.editor'), # legacy config.get('core.editor'), os.environ.get('VISUAL'), os.environ.get('EDITOR'), 'vi']: if editor: return editor
def user(): """Return the user information. """ global __user if not __user: name = config.get('user.name') email = config.get('user.email') __user = Person(name, email) return __user
def clone(self, target_series): """Clones a series """ try: # allow cloning of branches not under StGIT control base = self.get_base() except: base = git.get_head() Series(target_series).init(create_at=base) new_series = Series(target_series) # generate an artificial description file new_series.set_description('clone of "%s"' % self.get_name()) # clone self's entire series as unapplied patches try: # allow cloning of branches not under StGIT control applied = self.get_applied() unapplied = self.get_unapplied() patches = applied + unapplied patches.reverse() except: patches = applied = unapplied = [] for p in patches: patch = self.get_patch(p) newpatch = new_series.new_patch( p, message=patch.get_description(), can_edit=False, unapplied=True, bottom=patch.get_bottom(), top=patch.get_top(), author_name=patch.get_authname(), author_email=patch.get_authemail(), author_date=patch.get_authdate(), ) if patch.get_log(): out.info("Setting log to %s" % patch.get_log()) newpatch.set_log(patch.get_log()) else: out.info("No log for %s" % p) # fast forward the cloned series to self's top new_series.forward_patches(applied) # Clone parent informations value = config.get("branch.%s.remote" % self.get_name()) if value: config.set("branch.%s.remote" % target_series, value) value = config.get("branch.%s.merge" % self.get_name()) if value: config.set("branch.%s.merge" % target_series, value) value = config.get("branch.%s.stgit.parentbranch" % self.get_name()) if value: config.set("branch.%s.stgit.parentbranch" % target_series, value)
def clone(self, target_series): """Clones a series """ try: # allow cloning of branches not under StGIT control base = self.get_base() except BaseException: base = git.get_head() Series(target_series).init(create_at=base) new_series = Series(target_series) # generate an artificial description file new_series.set_description('clone of "%s"' % self.get_name()) # clone self's entire series as unapplied patches try: # allow cloning of branches not under StGIT control applied = self.get_applied() unapplied = self.get_unapplied() patches = applied + unapplied patches.reverse() except BaseException: patches = applied = unapplied = [] for p in patches: patch = self.get_patch(p) newpatch = new_series.new_patch( p, message=patch.get_description(), can_edit=False, unapplied=True, bottom=patch.get_bottom(), top=patch.get_top(), author_name=patch.get_authname(), author_email=patch.get_authemail(), author_date=patch.get_authdate(), ) if patch.get_log(): out.info('Setting log to %s' % patch.get_log()) newpatch.set_log(patch.get_log()) else: out.info('No log for %s' % p) # fast forward the cloned series to self's top new_series.forward_patches(applied) # Clone parent informations value = config.get('branch.%s.remote' % self.get_name()) if value: config.set('branch.%s.remote' % target_series, value) value = config.get('branch.%s.merge' % self.get_name()) if value: config.set('branch.%s.merge' % target_series, value) value = config.get('branch.%s.stgit.parentbranch' % self.get_name()) if value: config.set('branch.%s.stgit.parentbranch' % target_series, value)
def user(): """Return the user information. """ global __user if not __user: name=config.get('user.name') email=config.get('user.email') __user = Person(name, email) return __user;
def get_editor(): for editor in [ environ_get('GIT_EDITOR'), config.get('stgit.editor'), # legacy config.get('core.editor'), environ_get('VISUAL'), environ_get('EDITOR'), 'vi', ]: if editor: return editor
def get_editor(): for editor in [ os.environ.get("GIT_EDITOR"), config.get("stgit.editor"), # legacy config.get("core.editor"), os.environ.get("VISUAL"), os.environ.get("EDITOR"), "vi", ]: if editor: return editor
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 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 __send_message(msg_type, tmpl, options, *args): """Message sending dispatcher. """ msg_id = email.utils.make_msgid('stgit', domain=options.domain or config.get('stgit.domain')) if msg_type == 'cover': assert len(args) == 1, 'too many args for msg_type == "cover"' patches = args[0] msg = __build_cover(tmpl, msg_id, options, patches) outstr = 'the cover message' elif msg_type == 'patch': patch, patch_nr, total_nr, ref_id = args msg = __build_message(tmpl, msg_id, options, patch, patch_nr, total_nr, ref_id) outstr = 'patch "%s"' % patch else: raise AssertionError('invalid msg_type: %s' % msg_type) # pragma: no cover msg_bytes = msg.as_bytes(options.mbox) if options.mbox: out.stdout_bytes(msg_bytes + b'\n') return msg_id if not options.git: from_addr, to_addrs = __parse_addresses(msg) out.start('Sending ' + outstr) smtpserver = options.smtp_server or config.get('stgit.smtpserver') if options.git: __send_message_git(msg_bytes, msg['From'], options) elif smtpserver.startswith('/'): # Use the sendmail tool __send_message_sendmail(smtpserver, msg_bytes) else: # Use the SMTP server (we have host and port information) __send_message_smtp(smtpserver, from_addr, to_addrs, msg_bytes, options) # give recipients a chance of receiving related patches in correct order if msg_type == 'cover' or (msg_type == 'patch' and patch_nr < total_nr): sleep = options.sleep or config.getint('stgit.smtpdelay') time.sleep(sleep) if not options.git: out.done() return msg_id
def interactive_merge(filename): """Run the interactive merger on the given file. Note that the index should not have any conflicts. """ extensions = file_extensions() ancestor = filename + extensions['ancestor'] current = filename + extensions['current'] patched = filename + extensions['patched'] if os.path.isfile(ancestor): three_way = True files_dict = { 'branch1': current, 'ancestor': ancestor, 'branch2': patched, 'output': filename } imerger = config.get('stgit.i3merge') else: three_way = False files_dict = { 'branch1': current, 'branch2': patched, 'output': filename } imerger = config.get('stgit.i2merge') if not imerger: raise GitMergeException, 'No interactive merge command configured' # check whether we have all the files for the merge for fn in [filename, current, patched]: if not os.path.isfile(fn): raise GitMergeException, \ 'Cannot run the interactive merge: "%s" missing' % fn mtime = os.path.getmtime(filename) out.info('Trying the interactive %s merge' % (three_way and 'three-way' or 'two-way')) err = os.system(imerger % files_dict) if err != 0: raise GitMergeException, 'The interactive merge failed: %d' % err if not os.path.isfile(filename): raise GitMergeException, 'The "%s" file is missing' % filename if mtime == os.path.getmtime(filename): raise GitMergeException, 'The "%s" file was not modified' % filename
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') if __run(command, args) != 0: raise GitException, 'Failed "%s %s"' % (command, repository)
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') if __run(command, args) != 0: raise GitException, 'Failed "%s %s"' % (command, repository)
def update_commit_data(cd, options): """Return a new CommitData object updated according to the command line options.""" # Set the commit message from commandline. if options.message is not None: cd = cd.set_message(options.message) # Modify author data. cd = cd.set_author(options.author(cd.author)) # Add Signed-off-by: or similar. if options.sign_str != None: sign_str = options.sign_str else: sign_str = config.get("stgit.autosign") if sign_str != None: cd = cd.set_message( add_sign_line(cd.message, sign_str, cd.committer.name, cd.committer.email)) # Let user edit the commit message manually, unless # --save-template or --message was specified. if not getattr(options, 'save_template', None) and options.message is None: cd = cd.set_message(edit_string(cd.message, '.stgit-new.txt')) return cd
def __build_address_headers(msg, options, extra_cc = []): """Build the address headers and check existing headers in the template. """ def __replace_header(header, addr): if addr: crt_addr = msg[header] del msg[header] if crt_addr: msg[header] = address_or_alias(', '.join([crt_addr, addr])) else: msg[header] = address_or_alias(addr) to_addr = '' cc_addr = '' bcc_addr = '' autobcc = config.get('stgit.autobcc') or '' if options.to: to_addr = ', '.join(options.to) if options.cc: cc_addr = ', '.join(options.cc + extra_cc) elif extra_cc: cc_addr = ', '.join(extra_cc) if options.bcc: bcc_addr = ', '.join(options.bcc + [autobcc]) elif autobcc: bcc_addr = autobcc __replace_header('To', to_addr) __replace_header('Cc', cc_addr) __replace_header('Bcc', bcc_addr)
def __create_branch(branch_name, committish): repository = directory.repository branch_commit = None if committish is not None: parentbranch = None try: branchpoint = repository.run( ['git', 'rev-parse', '--symbolic-full-name', committish] ).output_one_line() if branchpoint.startswith('refs/heads/') or branchpoint.startswith( 'refs/remotes/' ): # committish is a valid ref from the branchpoint setting above parentbranch = committish except RunException: out.info( 'Do not know how to determine parent branch from "%s"' % committish ) # exception in branch = rev_parse() leaves branchpoint unbound branchpoint = None branch_commit = git_commit(branchpoint or committish, repository) if parentbranch: out.info('Recording "%s" as parent branch' % parentbranch) else: out.info( 'Do not know how to determine parent branch from "%s"' % committish ) else: try: # branch stack off current branch parentbranch = repository.head_ref except DetachedHeadException: parentbranch = None if parentbranch: parentremote = config.get('branch.%s.remote' % parentbranch) if parentremote: out.info('Using remote "%s" to pull parent from' % parentremote) else: out.info('Recording as a local branch') else: # no known parent branch, can't guess the remote parentremote = None stack = Stack.create( repository, name=branch_name, msg='create %s' % branch_name, create_at=branch_commit, parent_remote=parentremote, parent_branch=parentbranch, switch_to=True, ) return stack
def __build_address_headers(msg, options, extra_cc=[]): """Build the address headers and check existing headers in the template. """ def __replace_header(header, addr): if addr: crt_addr = msg[header] del msg[header] if crt_addr: msg[header] = address_or_alias(', '.join([crt_addr, addr])) else: msg[header] = address_or_alias(addr) to_addr = '' cc_addr = '' bcc_addr = '' autobcc = config.get('stgit.autobcc') or '' if options.to: to_addr = ', '.join(options.to) if options.cc: cc_addr = ', '.join(options.cc + extra_cc) elif extra_cc: cc_addr = ', '.join(extra_cc) if options.bcc: bcc_addr = ', '.join(options.bcc + [autobcc]) elif autobcc: bcc_addr = autobcc __replace_header('To', to_addr) __replace_header('Cc', cc_addr) __replace_header('Bcc', bcc_addr)
def patch_desc(repo, cd, append_diff, diff_flags, replacement_diff): """Return a description text for the patch. The returned description is suitable for editing and/or reimporting with :func:`update_patch_description()`. :param cd: the :class:`stgit.lib.git.CommitData` to generate a description of :param append_diff: whether to append the patch diff to the description :type append_diff: bool :param diff_flags: extra parameters to pass to `git diff` :param replacement_diff: diff text to use; or None if it should be computed from cd :type replacement_diff: str or None """ commit_encoding = config.get('i18n.commitencoding') desc = '\n'.join([ 'From: %s' % cd.author.name_email, 'Date: %s' % cd.author.date.isoformat(), '', cd.message_str, ]).encode(commit_encoding) if append_diff: parts = [desc.rstrip(), b'', b'---', b''] if replacement_diff: parts.append(replacement_diff) else: diff = repo.diff_tree(cd.parent.data.tree, cd.tree, diff_flags) if diff: diffstat = repo.default_iw.diffstat(diff).encode( commit_encoding) parts.extend([diffstat, diff]) desc = b'\n'.join(parts) return desc
def update_commit_data(cd, options): """Return a new CommitData object updated according to the command line options.""" # Set the commit message from commandline. if options.message is not None: cd = cd.set_message(options.message) # Modify author data. cd = cd.set_author(options.author(cd.author)) # Add Signed-off-by: or similar. if options.sign_str is not None: sign_str = options.sign_str else: sign_str = config.get("stgit.autosign") if sign_str is not None: cd = cd.set_message( add_sign_line(cd.message, sign_str, cd.committer.name, cd.committer.email)) # Let user edit the commit message manually, unless # --save-template or --message was specified. if not getattr(options, 'save_template', None) and options.message is None: tmpl = templates.get_template('patchdescr.tmpl') if tmpl: cd = cd.set_message(cd.message + tmpl) cd = cd.set_message(edit_string(cd.message, '.stgit-new.txt')) return cd
def update_commit_data(cd, message=None, author=None, sign_str=None, edit=False): """Return a new CommitData object updated according to the command line options.""" # Set the commit message from commandline. if message is not None: cd = cd.set_message(message) # Modify author data. if author is not None: cd = cd.set_author(author) # Add Signed-off-by: or similar. if sign_str is None: sign_str = config.get("stgit.autosign") if sign_str: cd = cd.set_message( add_trailer(cd.message_str, sign_str, cd.committer.name, cd.committer.email)) if edit: tmpl = templates.get_template('patchdescr.tmpl') if tmpl: cd = cd.set_message(cd.message + tmpl) cd = cd.set_message(edit_bytes(cd.message, '.stgit-new.txt')) return cd
def set_message(self, message): commit_encoding = config.get('i18n.commitencoding') if isinstance(message, bytes): message.decode(commit_encoding) else: message = message.encode(commit_encoding) return self._replace(message=message, encoding=commit_encoding)
def __build_address_headers(msg, options, extra_cc=[]): """Build the address headers and check existing headers in the template. """ to_addr = '' cc_addr = '' extra_cc_addr = '' bcc_addr = '' autobcc = config.get('stgit.autobcc') or '' if options.to: to_addr = ', '.join(options.to) if options.cc: cc_addr = ', '.join(options.cc) if extra_cc: extra_cc_addr = ', '.join(extra_cc) if options.bcc: bcc_addr = ', '.join(options.bcc + [autobcc]) elif autobcc: bcc_addr = autobcc # if an address is on a header, ignore it from the rest from_set = __update_header(msg, 'From') to_set = __update_header(msg, 'To', to_addr) # --auto generated addresses, don't include the sender __update_header(msg, 'Cc', extra_cc_addr, from_set) cc_set = __update_header(msg, 'Cc', cc_addr, to_set) __update_header(msg, 'Bcc', bcc_addr, to_set.union(cc_set))
def patch_desc(repo, cd, append_diff, diff_flags, replacement_diff): """Return a description text for the patch, suitable for editing and/or reimporting with L{update_patch_description()}. @param cd: The L{CommitData<stgit.lib.git.CommitData>} to generate a description of @param append_diff: Whether to append the patch diff to the description @type append_diff: C{bool} @param diff_flags: Extra parameters to pass to C{git diff} @param replacement_diff: Diff text to use; or C{None} if it should be computed from C{cd} @type replacement_diff: C{str} or C{None}""" commit_encoding = config.get('i18n.commitencoding') desc = '\n'.join([ 'From: %s' % cd.author.name_email, 'Date: %s' % cd.author.date.isoformat(), '', cd.message_str, ]).encode(commit_encoding) if append_diff: desc += b'\n---\n' if replacement_diff: desc += b'\n' + replacement_diff else: diff = repo.diff_tree(cd.parent.data.tree, cd.tree, diff_flags) if diff: desc += b'\n'.join( [b'', diffstat(diff).encode(commit_encoding), diff]) return desc
def __build_address_headers(msg, options, extra_cc = []): """Build the address headers and check existing headers in the template. """ to_addr = '' cc_addr = '' extra_cc_addr = '' bcc_addr = '' autobcc = config.get('stgit.autobcc') or '' if options.to: to_addr = ', '.join(options.to) if options.cc: cc_addr = ', '.join(options.cc) if extra_cc: extra_cc_addr = ', '.join(extra_cc) if options.bcc: bcc_addr = ', '.join(options.bcc + [autobcc]) elif autobcc: bcc_addr = autobcc # if an address is on a header, ignore it from the rest from_set = __update_header(msg, 'From') to_set = __update_header(msg, 'To', to_addr) # --auto generated addresses, don't include the sender __update_header(msg, 'Cc', extra_cc_addr, from_set) cc_set = __update_header(msg, 'Cc', cc_addr, to_set) bcc_set = __update_header(msg, 'Bcc', bcc_addr, to_set.union(cc_set))
def __init__(self, tree, parents, message, encoding=None, author=None, committer=None): self.tree = tree self.parents = parents self.encoding = (encoding if encoding is not None else config.get('i18n.commitencoding')) if isinstance(message, bytes): self.message = message else: self.message = message.encode(self.encoding) if author is None: self._author = Person.author() else: assert isinstance(author, (Person, bytes)) self._author = author if committer is None: self._committer = Person.committer() else: assert isinstance(committer, (Person, bytes)) self._committer = committer
def get_patch_description(repo, cd, patch_name, append_diff, diff_flags): """Return a description text for the patch. The returned description is suitable for editing and/or reimporting with :func:`_update_patch_description()`. :param cd: the :class:`stgit.lib.git.CommitData` to generate a description of :param append_diff: whether to append the patch diff to the description :type append_diff: bool :param diff_flags: extra parameters to pass to `git diff` """ commit_encoding = config.get('i18n.commitencoding') desc = '\n'.join([ 'Patch: %s' % patch_name, 'From: %s' % cd.author.name_email, 'Date: %s' % cd.author.date.isoformat(), '', cd.message_str, EDIT_MESSAGE_INSTRUCTIONS, ]).encode(commit_encoding) if append_diff: parts = [desc.rstrip(), b'---', b''] diff = repo.diff_tree(cd.parent.data.tree, cd.tree, diff_flags, binary=False, full_index=True) if diff: diffstat = repo.default_iw.diffstat(diff).encode(commit_encoding) parts.extend([diffstat, diff]) desc = b'\n'.join(parts) return desc
def __build_cover(tmpl, msg_id, options, patches): """Build the cover message (series description) to be sent via SMTP """ sender = __get_sender() if options.version: version_str = '%s' % options.version version_space = ' ' else: version_str = '' version_space = '' if options.prefix: prefix_str = options.prefix else: prefix_str = config.get('stgit.mail.prefix') if prefix_str: prefix_space = ' ' else: prefix_str = '' prefix_space = '' total_nr_str = str(len(patches)) patch_nr_str = '0'.zfill(len(total_nr_str)) if len(patches) > 1: number_str = '%s/%s' % (patch_nr_str, total_nr_str) number_space = ' ' else: number_str = '' number_space = '' tmpl_dict = {'sender': sender, # for backward template compatibility 'maintainer': sender, # for backward template compatibility 'endofheaders': '', # for backward template compatibility 'date': '', 'version': version_str, 'vspace': version_space, 'prefix': prefix_str, 'pspace': prefix_space, 'patchnr': patch_nr_str, 'totalnr': total_nr_str, 'number': number_str, 'nspace': number_space, 'snumber': number_str.strip(), 'shortlog': stack.shortlog(crt_series.get_patch(p) for p in reversed(patches)), 'diffstat': gitlib.diffstat(git.diff( rev1 = git_id(crt_series, '%s^' % patches[0]), rev2 = git_id(crt_series, '%s' % patches[-1]), diff_flags = options.diff_flags))} try: msg_string = tmpl % tmpl_dict except KeyError, err: raise CmdException, 'Unknown patch template variable: %s' \ % err
def parent_remote(self): remote = config.get(self._remote_key) if remote is None: raise BranchException( '%s: no parent remote; consider configuring "%s"' % (self.name, self._remote_key)) else: return remote
def get_hooks_path(repository): hooks_path = config.get('core.hookspath') if hooks_path is None: return os.path.join(repository.directory, 'hooks') elif os.path.isabs(hooks_path): return hooks_path else: return os.path.join(repository.default_worktree.directory, hooks_path)
def keep_option(): return [ opt('-k', '--keep', action='store_true', short='Keep the local changes', default=config.get('stgit.autokeep') == 'yes') ]
def interactive_merge(filename): """Run the interactive merger on the given file. Note that the index should not have any conflicts. """ extensions = file_extensions() ancestor = filename + extensions['ancestor'] current = filename + extensions['current'] patched = filename + extensions['patched'] if os.path.isfile(ancestor): three_way = True files_dict = {'branch1': current, 'ancestor': ancestor, 'branch2': patched, 'output': filename} imerger = config.get('stgit.i3merge') else: three_way = False files_dict = {'branch1': current, 'branch2': patched, 'output': filename} imerger = config.get('stgit.i2merge') if not imerger: raise GitMergeException, 'No interactive merge command configured' # check whether we have all the files for the merge for fn in [filename, current, patched]: if not os.path.isfile(fn): raise GitMergeException, \ 'Cannot run the interactive merge: "%s" missing' % fn mtime = os.path.getmtime(filename) out.info('Trying the interactive %s merge' % (three_way and 'three-way' or 'two-way')) err = os.system(imerger % files_dict) if err != 0: raise GitMergeException, 'The interactive merge failed: %d' % err if not os.path.isfile(filename): raise GitMergeException, 'The "%s" file is missing' % filename if mtime == os.path.getmtime(filename): raise GitMergeException, 'The "%s" file was not modified' % filename
def pull(repository, remote_repository): """Pull changes from remote repository using 'git pull' by default.""" command = (config.get( 'branch.%s.stgit.pullcmd' % repository.current_branch_name) or config.get('stgit.pullcmd')).split() command.append(remote_repository) runner = repository.run(command) # Assumption: pullcmd will return 1 for a conflict and some non-1 code for # other errors. This is probably an incorrect assumption because pullcmd # may not be `git pull` and even `git pull` does not make any clear # guarantees about its return code. However, the consequence of any of this # going wrong will be that `stg pull` will return 3 (conflict) when 2 # (generic error) might be more correct. runner.returns([0, 1]).run() if runner.exitcode: raise MergeConflictException([]) repository.refs.reset_cache()
def rebase(stack, iw, target_commit=None): command = (config.get('branch.%s.stgit.rebasecmd' % stack.name) or config.get('stgit.rebasecmd')) if not command and not target_commit: raise CmdException('Default rebasing requires a commit') elif target_commit: out.start('Rebasing to "%s"' % target_commit.sha1) else: out.start('Rebasing to the default target') if command: command = command.split() if target_commit is not None: command.append(target_commit.sha1) iw.run(command).run() else: iw.checkout_hard(target_commit) stack.set_head(target_commit, 'rebase') out.done()
def keep_option(): return [ opt( "-k", "--keep", action="store_true", short="Keep the local changes", default=config.get("stgit.autokeep") == "yes", ) ]
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)
def __address_or_alias(addr): if not addr: return None if addr.find('@') >= 0: # it's an e-mail address return addr alias = config.get('mail.alias.'+addr) if alias: # it's an alias return alias raise CmdException, 'unknown e-mail alias: %s' % addr
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 = tree_id)
def get_parent_branch(self): value = config.get("branch.%s.stgit.parentbranch" % self.get_name()) if value: return value elif git.rev_parse("heads/origin"): out.note( ('No parent branch declared for stack "%s",' ' defaulting to "heads/origin".' % self.get_name()), ('Consider setting "branch.%s.stgit.parentbranch"' ' with "git config".' % self.get_name()), ) return "heads/origin" else: raise StackException, 'Cannot find a parent branch for "%s"' % self.get_name()
def diff_opts_option(): def diff_opts_callback(option, opt_str, value, parser): if value: parser.values.diff_flags.extend(value.split()) else: parser.values.diff_flags = [] return [ opt('-O', '--diff-opts', dest = 'diff_flags', default = (config.get('stgit.diff-opts') or '').split(), action = 'callback', callback = diff_opts_callback, type = 'string', metavar = 'OPTIONS', args = [strings('-M', '-C')], short = 'Extra options to pass to "git diff"')]
def address_or_alias(addr_pair): """Return a name-email tuple the e-mail address is valid or look up the aliases in the config files. """ addr = addr_pair[1] if "@" in addr: # it's an e-mail address return addr_pair alias = config.get("mail.alias." + addr) if alias: # it's an alias return name_email(alias) raise CmdException, "unknown e-mail alias: %s" % addr
def __set_smtp_credentials(options): """Set the (smtpuser, smtppassword, smtpusetls) credentials if the method of sending is SMTP. """ global __smtp_credentials smtpserver = options.smtp_server or config.get('stgit.smtpserver') if options.mbox or options.git or smtpserver.startswith('/'): return smtppassword = options.smtp_password or config.get('stgit.smtppassword') smtpuser = options.smtp_user or config.get('stgit.smtpuser') smtpusetls = options.smtp_tls or config.get('stgit.smtptls') == 'yes' if (smtppassword and not smtpuser): raise CmdException('SMTP password supplied, username needed') if (smtpusetls and not smtpuser): raise CmdException('SMTP over TLS requested, username needed') if (smtpuser and not smtppassword): smtppassword = getpass.getpass("Please enter SMTP password: ") __smtp_credentials = (smtpuser, smtppassword, smtpusetls)
def address_or_alias(addr_pair): """Return a name-email tuple the e-mail address is valid or look up the aliases in the config files. """ addr = addr_pair[1] if '@' in addr: # it's an e-mail address return addr_pair alias = config.get('mail.alias.' + addr) if alias: # it's an alias return name_email(alias) raise CmdException('unknown e-mail alias: %s' % addr)
def get_parent_remote(self): value = config.get('branch.%s.remote' % self.get_name()) if value: return value elif 'origin' in git.remotes_list(): out.note(('No parent remote declared for stack "%s",' ' defaulting to "origin".' % self.get_name()), ('Consider setting "branch.%s.remote" and' ' "branch.%s.merge" with "git config".' % (self.get_name(), self.get_name()))) return 'origin' else: raise StackException, 'Cannot find a parent remote for "%s"' % self.get_name()
def __build_address_headers(msg, options, extra_cc = []): """Build the address headers and check existing headers in the template. """ def __addr_pairs(msg, header, extra): pairs = email.Utils.getaddresses(msg.get_all(header, []) + extra) # remove pairs without an address and resolve the aliases return [address_or_alias(p) for p in pairs if p[1]] def __update_header(header, addr = '', ignore = ()): addr_pairs = __addr_pairs(msg, header, [addr]) del msg[header] # remove the duplicates and filter the addresses addr_dict = dict((addr, email.Utils.formataddr((name, addr))) for name, addr in addr_pairs if addr not in ignore) if addr_dict: msg[header] = ', '.join(addr_dict.itervalues()) return set(addr_dict.iterkeys()) to_addr = '' cc_addr = '' extra_cc_addr = '' bcc_addr = '' autobcc = config.get('stgit.autobcc') or '' if options.to: to_addr = ', '.join(options.to) if options.cc: cc_addr = ', '.join(options.cc) if extra_cc: extra_cc_addr = ', '.join(extra_cc) if options.bcc: bcc_addr = ', '.join(options.bcc + [autobcc]) elif autobcc: bcc_addr = autobcc # if an address is on a header, ignore it from the rest to_set = __update_header('To', to_addr) cc_set = __update_header('Cc', cc_addr, to_set) bcc_set = __update_header('Bcc', bcc_addr, to_set.union(cc_set)) # --auto generated addresses, don't include the sender from_set = __update_header('From') __update_header('Cc', extra_cc_addr, to_set.union(bcc_set).union(from_set)) # update other address headers __update_header('Reply-To') __update_header('Mail-Reply-To') __update_header('Mail-Followup-To')
def __get_sender(): """Return the 'authname <authemail>' string as read from the configuration file """ sender=config.get('stgit.sender') if not sender: try: sender = str(git.user()) except git.GitException: sender = str(git.author()) if not sender: raise CmdException, 'unknown sender details' return address_or_alias(sender)
def merge_recursive(base, head1, head2): """Perform a 3-way merge between base, head1 and head2 into the local tree """ refresh_index() p = GRun('merge-recursive', base, '--', head1, head2).env( { 'GITHEAD_%s' % base: 'ancestor', 'GITHEAD_%s' % head1: 'current', 'GITHEAD_%s' % head2: 'patched'}).returns([0, 1]) output = p.output_lines() if p.exitcode: # There were conflicts if config.get('stgit.autoimerge') == 'yes': mergetool() else: conflicts = [l for l in output if l.startswith('CONFLICT')] out.info(*conflicts) raise GitException, "%d conflict(s)" % len(conflicts)
def call_editor(filename): """Run the editor on the specified filename.""" # the editor editor = config.get('stgit.editor') if editor: pass elif 'EDITOR' in os.environ: editor = os.environ['EDITOR'] else: editor = 'vi' editor += ' %s' % filename out.start('Invoking the editor: "%s"' % editor) err = os.system(editor) if err: raise EditorException, 'editor failed, exit code: %d' % err out.done()
def __build_cover(tmpl, total_nr, msg_id, options): """Build the cover message (series description) to be sent via SMTP """ sender = __get_sender() if options.version: version_str = ' %s' % options.version else: version_str = '' if options.prefix: prefix_str = options.prefix + ' ' else: confprefix = config.get('stgit.mail.prefix') if confprefix: prefix_str = confprefix + ' ' else: prefix_str = '' total_nr_str = str(total_nr) patch_nr_str = '0'.zfill(len(total_nr_str)) if total_nr > 1: number_str = ' %s/%s' % (patch_nr_str, total_nr_str) else: number_str = '' tmpl_dict = {'sender': sender, # for backward template compatibility 'maintainer': sender, # for backward template compatibility 'endofheaders': '', # for backward template compatibility 'date': '', 'version': version_str, 'prefix': prefix_str, 'patchnr': patch_nr_str, 'totalnr': total_nr_str, 'number': number_str} try: msg_string = tmpl % tmpl_dict except KeyError, err: raise CmdException, 'Unknown patch template variable: %s' \ % err
def __get_sender(): """Return the 'authname <authemail>' string as read from the configuration file """ sender = config.get('stgit.sender') if not sender: try: sender = git.user() except git.GitException: try: sender = git.author() except git.GitException: pass if not sender: raise CmdException('Unknown sender name and e-mail; you should for ' 'example set git config user.name and user.email') sender = email.utils.parseaddr(sender) return email.utils.formataddr(address_or_alias(sender))
def push_patch(self, pn, iw = None, allow_interactive = False, already_merged = False): """Attempt to push the named patch. If this results in conflicts, halts the transaction. If index+worktree are given, spill any conflicts to them.""" out.start('Pushing patch "%s"' % pn) orig_cd = self.patches[pn].data cd = orig_cd.set_committer(None) oldparent = cd.parent cd = cd.set_parent(self.top) if already_merged: # the resulting patch is empty tree = cd.parent.data.tree else: base = oldparent.data.tree ours = cd.parent.data.tree theirs = cd.tree tree, self.temp_index_tree = self.temp_index.merge( base, ours, theirs, self.temp_index_tree) s = '' merge_conflict = False if not tree: if iw == None: self.__halt('%s does not apply cleanly' % pn) try: self.__checkout(ours, iw, allow_bad_head = False) except git.CheckoutException: self.__halt('Index/worktree dirty') try: interactive = (allow_interactive and config.get('stgit.autoimerge') == 'yes') iw.merge(base, ours, theirs, interactive = interactive) tree = iw.index.write_tree() self.__current_tree = tree s = 'modified' except git.MergeConflictException, e: tree = ours merge_conflict = True self.__conflicts = e.conflicts s = 'conflict' except git.MergeException, e: self.__halt(str(e))
def diff_opts_option(): def diff_opts_callback(option, opt_str, value, parser): if value: parser.values.diff_flags.extend(value.split()) else: parser.values.diff_flags = [] return [ opt( "-O", "--diff-opts", dest="diff_flags", default=(config.get("stgit.diff-opts") or "").split(), action="callback", callback=diff_opts_callback, type="string", metavar="OPTIONS", args=[strings("-M", "-C")], short='Extra options to pass to "git diff"', ) ]
def __send_message(type, tmpl, options, *args): """Message sending dispatcher. """ (build, outstr) = {'cover': (__build_cover, 'the cover message'), 'patch': (__build_message, 'patch "%s"' % args[0])}[type] if type == 'patch': (patch_nr, total_nr) = (args[1], args[2]) msg_id = email.Utils.make_msgid('stgit') msg = build(tmpl, msg_id, options, *args) msg_str = msg.as_string(options.mbox) if options.mbox: out.stdout_raw(msg_str + '\n') return msg_id if not options.git: from_addr, to_addrs = __parse_addresses(msg) out.start('Sending ' + outstr) smtpserver = options.smtp_server or config.get('stgit.smtpserver') if options.git: __send_message_git(msg, options) elif smtpserver.startswith('/'): # Use the sendmail tool __send_message_sendmail(smtpserver, msg_str) else: # Use the SMTP server (we have host and port information) __send_message_smtp(smtpserver, from_addr, to_addrs, msg_str, options) # give recipients a chance of receiving related patches in correct order if type == 'cover' or (type == 'patch' and patch_nr < total_nr): sleep = options.sleep or config.getint('stgit.smtpdelay') time.sleep(sleep) if not options.git: out.done() return msg_id
def func(parser, options, args): """Show the patch series """ if options.all and options.short: raise common.CmdException, 'combining --all and --short is meaningless' stack = directory.repository.get_stack(options.branch) if options.missing: cmp_stack = stack stack = directory.repository.get_stack(options.missing) # current series patches applied = unapplied = hidden = () if options.applied or options.unapplied or options.hidden: if options.all: raise common.CmdException, \ '--all cannot be used with --applied/unapplied/hidden' if options.applied: applied = stack.patchorder.applied if options.unapplied: unapplied = stack.patchorder.unapplied if options.hidden: hidden = stack.patchorder.hidden elif options.all: applied = stack.patchorder.applied unapplied = stack.patchorder.unapplied hidden = stack.patchorder.hidden else: applied = stack.patchorder.applied unapplied = stack.patchorder.unapplied if options.missing: cmp_patches = cmp_stack.patchorder.all else: cmp_patches = () # the filtering range covers the whole series if args: show_patches = parse_patches(args, applied + unapplied + hidden, len(applied)) else: show_patches = applied + unapplied + hidden # missing filtering show_patches = [p for p in show_patches if p not in cmp_patches] # filter the patches applied = [p for p in applied if p in show_patches] unapplied = [p for p in unapplied if p in show_patches] hidden = [p for p in hidden if p in show_patches] if options.short: nr = int(config.get('stgit.shortnr')) if len(applied) > nr: applied = applied[-(nr+1):] n = len(unapplied) if n > nr: unapplied = unapplied[:nr] elif n < nr: hidden = hidden[:nr-n] patches = applied + unapplied + hidden if options.count: out.stdout(len(patches)) return if not patches: return if options.showbranch: branch_str = stack.name + ':' else: branch_str = '' max_len = 0 if len(patches) > 0: max_len = max([len(i + branch_str) for i in patches]) if applied: for p in applied[:-1]: __print_patch(stack, p, branch_str, '+ ', max_len, options, config.get("stgit.color.applied")) __print_patch(stack, applied[-1], branch_str, '> ', max_len, options, config.get("stgit.color.current")) for p in unapplied: __print_patch(stack, p, branch_str, '- ', max_len, options, config.get("stgit.color.unapplied")) for p in hidden: __print_patch(stack, p, branch_str, '! ', max_len, options, config.get("stgit.color.hidden"))