From 37a4d1bfabaca3dd947799f10c6cfc369a9edb5e Mon Sep 17 00:00:00 2001 From: Catalin Marinas Date: Fri, 16 Sep 2005 14:05:06 +0100 Subject: [PATCH] Allow 'import' to cherry-pick a commit object A commit object in the tree can be cherry-picked and imported into a separate StGIT patch. It also allow reverse-importing, i.e. cancelling an existing commit. Signed-off-by: Catalin Marinas --- stgit/commands/common.py | 16 +++++++-- stgit/commands/imprt.py | 91 +++++++++++++++++++++++++++++++++++++++--------- stgit/git.py | 11 ++++-- stgit/stack.py | 27 +++++++++++--- 4 files changed, 118 insertions(+), 27 deletions(-) diff --git a/stgit/commands/common.py b/stgit/commands/common.py index 86f62fb..c6d4535 100644 --- a/stgit/commands/common.py +++ b/stgit/commands/common.py @@ -117,8 +117,18 @@ def name_email(string): """Return a tuple consisting of the name and email parsed from a standard 'name ' string """ - names = re.split('([^<>]*)<([^<>]*)>', string) - if len(names) != 4: + str_list = re.findall('^(.*)\s+<(.*)>$', string) + if not str_list: raise CmdException, 'Incorrect "name " string: %s' % string - return tuple([names[1].strip(), names[2].strip()]) + return str_list[0] + +def name_email_date(string): + """Return a tuple consisting of the name, email and date parsed + from a 'name date' string + """ + str_list = re.findall('^(.*)\s+<(.*)>\s+(.*)$', string) + if not str_list: + raise CmdException, 'Incorrect "name date" string: %s' % string + + return str_list[0] diff --git a/stgit/commands/imprt.py b/stgit/commands/imprt.py index e75536b..6baf426 100644 --- a/stgit/commands/imprt.py +++ b/stgit/commands/imprt.py @@ -24,22 +24,33 @@ from stgit import stack, git help = 'import a GNU diff file as a new patch' -usage = """%prog [options] [] +usage = """%prog [options] [|] -Create a new patch and apply the given GNU diff file (or the standard -input). By default, the file name is used as the patch name but this -can be overriden with the '--name' option. The patch can either be a -normal file with the description at the top or it can have standard -mail format, the Subject, From and Date headers being used for -generating the patch information. +Create a new patch and import the given GNU diff file (defaulting to +the standard input) or a given commit object into it. By default, the +file name is used as the patch name but this can be overriden with the +'--name' option. -The patch description has to be separated from the data with a '---' -line. For a normal file, if no author information is given, the first -'Signed-off-by:' line is used.""" +The patch file can either be a normal file with the description at the +top or it can have standard mail format, the Subject, From and Date +headers being used for generating the patch information. The patch +description has to be separated from the data with a '---' line. For a +normal file, if no author information is given, the first +'Signed-off-by:' line is used. + +When a commit object is imported, the log and author information are +those of the commit object. Passing the '--reverse' option will cancel +an existing commit object.""" options = [make_option('-m', '--mail', help = 'import the patch from a standard e-mail file', action = 'store_true'), + make_option('-c', '--commit', + help = 'import a commit object as a patch', + action = 'store_true'), + make_option('--reverse', + help = 'reverse the commit object before importing', + action = 'store_true'), make_option('-n', '--name', help = 'use NAME as the patch name'), make_option('-e', '--edit', @@ -139,17 +150,12 @@ def __parse_patch(filename = None): return (descr, authname, authemail, authdate) -def func(parser, options, args): +def import_file(parser, options, args): """Import a GNU diff file as a new patch """ if len(args) > 1: parser.error('incorrect number of arguments') - - check_local_changes() - check_conflicts() - check_head_top_equal() - - if len(args) == 1: + elif len(args) == 1: filename = args[0] else: filename = None @@ -208,3 +214,54 @@ def func(parser, options, args): print 'done' print_crt_patch() + +def import_commit(parser, options, args): + """Import a commit object as a new patch + """ + if len(args) != 1: + parser.error('incorrect number of arguments') + + commit_id = args[0] + + if options.name: + patch = options.name + else: + raise CmdException, 'Unkown patch name' + + commit = git.Commit(commit_id) + + if not options.reverse: + bottom = commit.get_parent() + top = commit_id + else: + bottom = commit_id + top = commit.get_parent() + + message = commit.get_log() + author_name, author_email, author_date = \ + name_email_date(commit.get_author()) + + print 'Importing commit %s...' % commit_id, + sys.stdout.flush() + + crt_series.new_patch(patch, message = message, can_edit = False, + unapplied = True, bottom = bottom, top = top, + author_name = author_name, + author_email = author_email, + author_date = author_date) + crt_series.push_patch(patch) + + print 'done' + print_crt_patch() + +def func(parser, options, args): + """Import a GNU diff file or a commit object as a new patch + """ + check_local_changes() + check_conflicts() + check_head_top_equal() + + if options.commit: + import_commit(parser, options, args) + else: + import_file(parser, options, args) diff --git a/stgit/git.py b/stgit/git.py index bdc0e14..687bee2 100644 --- a/stgit/git.py +++ b/stgit/git.py @@ -45,6 +45,7 @@ class Commit: self.__id_hash = id_hash lines = _output_lines('git-cat-file commit %s' % id_hash) + self.__parents = [] for i in range(len(lines)): line = lines[i] if line == '\n': @@ -53,7 +54,7 @@ class Commit: if field[0] == 'tree': self.__tree = field[1] elif field[0] == 'parent': - self.__parent = field[1] + self.__parents.append(field[1]) if field[0] == 'author': self.__author = field[1] if field[0] == 'comitter': @@ -67,7 +68,10 @@ class Commit: return self.__tree def get_parent(self): - return self.__parent + return self.__parents[0] + + def get_parents(self): + return self.__parents def get_author(self): return self.__author @@ -75,6 +79,9 @@ class Commit: def get_committer(self): return self.__committer + def get_log(self): + return self.__log + # dictionary of Commit objects, used to avoid multiple calls to git __commits = dict() diff --git a/stgit/stack.py b/stgit/stack.py index 5c41dd5..2d3bd22 100644 --- a/stgit/stack.py +++ b/stgit/stack.py @@ -413,7 +413,9 @@ class Series: return commit_id - def new_patch(self, name, message = None, can_edit = True, show_patch = False, + def new_patch(self, name, message = None, can_edit = True, + unapplied = False, show_patch = False, + top = None, bottom = None, author_name = None, author_email = None, author_date = None, committer_name = None, committer_email = None): """Creates a new patch @@ -434,8 +436,16 @@ class Series: patch = Patch(name, self.__patch_dir) patch.create() - patch.set_bottom(head) - patch.set_top(head) + + if bottom: + patch.set_bottom(bottom) + else: + patch.set_bottom(head) + if top: + patch.set_top(top) + else: + patch.set_top(head) + patch.set_description(descr) patch.set_authname(author_name) patch.set_authemail(author_email) @@ -443,8 +453,15 @@ class Series: patch.set_commname(committer_name) patch.set_commemail(committer_email) - append_string(self.__applied_file, patch.get_name()) - self.__set_current(name) + if unapplied: + patches = [patch.get_name()] + self.get_unapplied() + + f = file(self.__unapplied_file, 'w+') + f.writelines([line + '\n' for line in patches]) + f.close() + else: + append_string(self.__applied_file, patch.get_name()) + self.__set_current(name) def delete_patch(self, name): """Deletes a patch -- 2.11.0