pq-rpm: implement --retain-history option
authorMarkus Lehtonen <markus.lehtonen@linux.intel.com>
Mon, 31 Mar 2014 12:18:34 +0000 (15:18 +0300)
committerMarkus Lehtonen <markus.lehtonen@linux.intel.com>
Thu, 5 Jun 2014 11:20:08 +0000 (14:20 +0300)
With this option defined gbp tries to preserve the history when
converting. That is, for each commit in the old branch create one
corresponding commit in the new orphan packaging branch. This works by
dumping packaging files and updating patches for each commit. However,
empty commits are not generated - these are caused e.g. by changes in
files that are ignored by patch-generation.

NOTE! Only valid for the 'convert' action.

Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
gbp/scripts/pq_rpm.py
tests/component/rpm/test_pq_rpm.py

index 7ee407d..d8fa88f 100755 (executable)
@@ -517,33 +517,25 @@ def convert_package(repo, options):
             gbp.log.info("Dropping branch '%s'" % new_branch)
             repo.delete_branch(new_branch)
 
-    # Dump and commit packaging files
+    # Determine "history"
+    if options.retain_history:
+        # Find first commit that has the spec file and list commits from there
+        try:
+            repo.show('%s:%s' % (upstream_commit, spec.specpath))
+            history = repo.get_commits(upstream_commit, old_packaging)
+        except GitRepositoryError:
+            history_start = repo.get_commits(upstream_commit, old_packaging,
+                                             spec.specpath)[-1]
+            history = repo.get_commits('%s^' % history_start, old_packaging)
+    else:
+        history = [repo.rev_parse(old_packaging)]
+    history.reverse()
+
+    # Do import
     gbp.log.info("Importing packaging files from branch '%s' to '%s'" %
                  (old_packaging, new_branch))
-    packaging_tree = '%s:%s' % (old_packaging, options.packaging_dir)
-    packaging_tmp = tempfile.mkdtemp(prefix='pack_', dir=options.tmp_dir)
-    dump_packaging_dir = os.path.join(packaging_tmp, options.new_packaging_dir)
-    dump_tree(repo, dump_packaging_dir, packaging_tree, with_submodules=False,
-              recursive=False)
-
-    msg = "Auto-import packaging files from branch '%s'" % old_packaging
-    repo.commit_dir(packaging_tmp, msg, new_branch, create_missing_branch=True)
-    repo.set_branch(new_branch)
-
-    # Generate patches
-    spec = SpecFile(os.path.join(options.new_packaging_dir, spec.specfile))
-    patches = update_patch_series(repo, spec, upstream_commit, old_packaging,
-                                  options)
-    patches = [os.path.join(options.new_packaging_dir, pat) for pat in patches]
-
-    # Commit paches and spec
-    if patches:
-        gbp.log.info("Committing patches and modified spec file to git")
-        repo.add_files('.', untracked=False)
-        repo.add_files(patches)
-        msg = "Auto-generated patches from branch '%s'" % old_packaging
-        repo.commit_staged(msg=msg)
-
+    convert_with_history(repo, upstream_commit, history, new_branch,
+                         spec.specfile, options)
     # Copy extra files
     import_extra_files(repo, old_packaging, options.import_files,
                        patch_ignore=False)
@@ -555,6 +547,59 @@ def convert_package(repo, options):
     gbp.log.info("Please check all files and test building the package!")
 
 
+def convert_with_history(repo, upstream, commits, new_branch, spec_fn, options):
+    """Auto-import packaging files and (auto-generated) patches"""
+
+    # Dump and commit packaging files
+    packaging_tree = '%s:%s' % (commits[0], options.packaging_dir)
+    packaging_tmp = tempfile.mkdtemp(prefix='pack_', dir=options.tmp_dir)
+    dump_packaging_dir = os.path.join(packaging_tmp, options.new_packaging_dir)
+    dump_tree(repo, dump_packaging_dir, packaging_tree, with_submodules=False,
+              recursive=False)
+
+    msg = "Auto-import packaging files\n\n" \
+          "Imported initial packaging files from commit '%s'" % (commits[0])
+    new_tree = repo.create_tree(packaging_tmp)
+    tip_commit = repo.commit_tree(new_tree, msg, [])
+
+    # Generate initial patches
+    spec = SpecFile(os.path.join(dump_packaging_dir, spec_fn))
+    update_patch_series(repo, spec, upstream, commits[0], options)
+    # Commit updated packaging files only if something was changed
+    new_tree = repo.create_tree(packaging_tmp)
+    if new_tree != repo.rev_parse(tip_commit + ':'):
+        msg = "Auto-generate patches\n\n" \
+              "Generated patches from\n'%s..%s'\n\n" \
+              "updating spec file and possibly removing old patches." \
+              % (upstream, commits[0])
+        tip_commit = repo.commit_tree(new_tree, msg, [tip_commit])
+
+    # Import rest of the commits
+    for commit in commits[1:]:
+        shutil.rmtree(dump_packaging_dir)
+        packaging_tree = '%s:%s' % (commit, options.packaging_dir)
+        dump_tree(repo, dump_packaging_dir, packaging_tree,
+                  with_submodules=False, recursive=False)
+        try:
+            spec = SpecFile(os.path.join(dump_packaging_dir, spec_fn))
+            update_patch_series(repo, spec, upstream, commit, options)
+        except (NoSpecError, GbpError):
+            gbp.log.warn("Failed to generate patches from '%s'" % commit)
+
+        new_tree = repo.create_tree(packaging_tmp)
+        if new_tree == repo.rev_parse(tip_commit + ':'):
+            gbp.log.info("Skipping commit '%s' which generated no change" %
+                         commit)
+        else:
+            info = repo.get_commit_info(commit)
+            msg = "%s\n\n%sAuto-imported by gbp from '%s'" % (info['subject'],
+                        info['body'], commit)
+            tip_commit = repo.commit_tree(new_tree, msg, [tip_commit])
+
+    repo.create_branch(new_branch, tip_commit)
+    repo.set_branch(new_branch)
+
+
 def opt_split_cb(option, opt_str, value, parser):
     """Split option string into a list"""
     setattr(parser.values, option.dest, value.split(','))
@@ -604,6 +649,10 @@ def main(argv):
     parser.add_config_file_option(option_name="import-files",
             dest="import_files", type="string", action="callback",
             callback=opt_split_cb)
+    parser.add_option("--retain-history", action="store_true",
+            help="When doing convert, preserve as much of the git history as "
+                 "possible, i.e. create one commit per commit. Only "
+                 "relevant for the 'convert' action.")
     parser.add_option("--export-rev", action="store", dest="export_rev",
             default="",
             help="Export patches from treeish object TREEISH instead of head "
index 968119e..ba8648d 100644 (file)
@@ -535,6 +535,22 @@ class TestPqRpm(RpmRepoTestBase):
                      '--new-packaging-dir=rpm']), 0)
         self._check_repo_state(repo, 'master-orphan', branches, files)
 
+    def test_option_retain_history(self):
+        """Test the --retain-history cmdline option"""
+        repo = self.init_test_repo('gbp-test2')
+        branches = repo.get_local_branches() + ['master-orphan']
+        files = ['packaging/', 'packaging/bar.tar.gz', 'packaging/foo.txt',
+                 'packaging/gbp-test2.spec', 'packaging/gbp-test2-alt.spec',
+                 'packaging/my.patch', 'packaging/0001-My-addition.patch',
+                 '.gbp.conf']
+        # Drop pre-existing master-orphan branch
+        repo.delete_branch('master-orphan')
+
+        # Convert with history
+        eq_(mock_pq(['convert', '--retain-history']), 0)
+        self._check_repo_state(repo, 'master-orphan', branches, files)
+        eq_(len(repo.get_commits('', 'master-orphan')), 7)
+
     def test_import_unapplicable_patch(self):
         """Test import when a patch does not apply"""
         repo = self.init_test_repo('gbp-test')