Subject: Re: bug?: stgit creates (unneccessary?) conflicts when pulling

From: "Catalin Marinas" <[email-masked]>
Date: Tue, 28 Feb 2006 15:00:22 +0000

On 27/02/06, Catalin Marinas <[email-masked]> wrote:
> An idea (untested, I don't even know whether it's feasible) would be to
> check which patches were merged by reverse-applying them starting with
> the last. In this situation, all the merged patches should just revert
> their changes. You only need to do a git-diff between the bottom and the
> top of the patch and git-apply the output (maybe without even modifying
> the tree). If this operation succeeds, the patch was integrated and you
> don't even need to push it.

I tried some simple tests with the idea above. I attached a patch if
you'd like to try (I won't push it to the main StGIT repository yet.
For safety reasons, it only skips the merged patches when pushing
them. A future version could simply delete the merged patches.

--
Catalin
Add a merged upstream test for pull and push

From: Catalin Marinas <[email-masked]>

This patch adds the --merged option to both pull and push commands. With
this option, these commands will first try to check which patches were
merged upstream by reverse-applying them in reverse order. This should
solve the situation where several patches modify the same line in a file.

Signed-off-by: Catalin Marinas <[email-masked]>
---

 stgit/commands/pull.py |   20 +++++++++++++++++++-
 stgit/commands/push.py |   17 ++++++++++++++++-
 stgit/git.py           |   12 +++++++++---
 stgit/stack.py         |   20 ++++++++++++++++++++
 4 files changed, 64 insertions(+), 5 deletions(-)

diff --git a/stgit/commands/pull.py b/stgit/commands/pull.py
index 843b579..5d75530 100644
--- a/stgit/commands/pull.py
+++ b/stgit/commands/pull.py
@@ -39,6 +39,9 @@ format."""
 
 options = [make_option('-n', '--nopush',
                        help = 'do not push the patches back after pulling',
+                       action = 'store_true'),
+           make_option('-m', '--merged',
+                       help = 'check for patches merged upstream (slower)',
                        action = 'store_true')]
 
 def func(parser, options, args):
@@ -77,12 +80,27 @@ def func(parser, options, args):
     # push the patches back
     if options.nopush:
         applied = []
+
+    # check for patches merged upstream
+    if options.merged:
+        merged = crt_series.merged_patches(patches)
+    else:
+        merged = []
+
     for p in applied:
+        if p in merged:
+            print 'Patch "%s" merged upstream' % p
+            continue
+
         print 'Pushing patch "%s"...' % p,
         sys.stdout.flush()
-        crt_series.push_patch(p)
+
+        modified = crt_series.push_patch(p)
+
         if crt_series.empty_patch(p):
             print 'done (empty patch)'
+        elif modified:
+            print 'done (modified)'
         else:
             print 'done'
 
diff --git a/stgit/commands/push.py b/stgit/commands/push.py
index 9924a78..72b2663 100644
--- a/stgit/commands/push.py
+++ b/stgit/commands/push.py
@@ -49,6 +49,9 @@ options = [make_option('-a', '--all',
            make_option('--reverse',
                        help = 'push the patches in reverse order',
                        action = 'store_true'),
+           make_option('-m', '--merged',
+                       help = 'check for patches merged upstream (slower)',
+                       action = 'store_true'),
            make_option('--undo',
                        help = 'undo the last push operation',
                        action = 'store_true')]
@@ -134,9 +137,21 @@ def func(parser, options, args):
     elif forwarded == 1:
         print 'Fast-forwarded patch "%s"' % patches[0]
 
-    for p in patches[forwarded:]:
+    patches = patches[forwarded:]
+
+    # check for patches merged upstream
+    if options.merged:
+        merged = crt_series.merged_patches(patches)
+    else:
+        merged = []
+
+    for p in patches:
         is_patch_appliable(p)
 
+        if p in merged:
+            print 'Patch "%s" merged upstream' % p
+            continue
+
         print 'Pushing patch "%s"...' % p,
         sys.stdout.flush()
 
diff --git a/stgit/git.py b/stgit/git.py
index 016bc3a..66b8612 100644
--- a/stgit/git.py
+++ b/stgit/git.py
@@ -462,14 +462,20 @@ def commit(message, files = None, parent
 
     return commit_id
 
-def apply_diff(rev1, rev2):
+def apply_diff(rev1, rev2, check_index = True):
     """Apply the diff between rev1 and rev2 onto the current
     index. This function doesn't need to raise an exception since it
     is only used for fast-pushing a patch. If this operation fails,
     the pushing would fall back to the three-way merge.
     """
-    return os.system('git-diff-tree -p %s %s | git-apply --index 2> /dev/null'
-                     % (rev1, rev2)) == 0
+    if check_index:
+        index_opt = '--index'
+    else:
+        index_opt = ''
+    cmd = 'git-diff-tree -p %s %s | git-apply %s 2> /dev/null' \
+          % (rev1, rev2, index_opt)
+
+    return os.system(cmd) == 0
 
 def merge(base, head1, head2):
     """Perform a 3-way merge between base, head1 and head2 into the
diff --git a/stgit/stack.py b/stgit/stack.py
index e1c55f0..9d5f043 100644
--- a/stgit/stack.py
+++ b/stgit/stack.py
@@ -780,6 +780,26 @@ class Series:
 
         return forwarded
 
+    def merged_patches(self, names):
+        """Test which patches were merged upstream by reverse-applying
+        them in reverse order. The function returns the list of
+        patches detected to have been applied. The state of the tree
+        is restored to the original one
+        """
+        patches = [Patch(name, self.__patch_dir, self.__refs_dir)
+                   for name in names]
+        patches.reverse()
+
+        merged = []
+        for p in patches:
+            if git.apply_diff(p.get_top(), p.get_bottom(), False):
+                merged.append(p.get_name())
+        merged.reverse()
+
+        git.reset()
+
+        return merged
+
     def push_patch(self, name):
         """Pushes a patch on the stack
         """