chiark / gitweb /
Initial commit (Release 0.4)
[stgit] / gitmergeonefile.py
1 #!/usr/bin/env python
2 """Performs a 3-way merge for GIT files
3 """
4
5 __copyright__ = """
6 Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com>
7
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License version 2 as
10 published by the Free Software Foundation.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 """
21
22 import sys, os
23 from stgit.config import config
24 from stgit.utils import append_string
25
26
27 #
28 # Options
29 #
30 try:
31     merger = config.get('gitmergeonefile', 'merger')
32 except Exception, err:
33     print >> sys.stderr, 'Configuration error: %s' % err
34     sys.exit(1)
35
36 if config.has_option('gitmergeonefile', 'keeporig'):
37     keeporig = config.get('gitmergeonefile', 'keeporig')
38 else:
39     keeporig = 'yes'
40
41
42 #
43 # Global variables
44 #
45 if 'GIT_DIR' in os.environ:
46     base_dir = os.environ['GIT_DIR']
47 else:
48     base_dir = '.git'
49
50
51 #
52 # Utility functions
53 #
54 def __str2none(x):
55     if x == '':
56         return None
57     else:
58         return x
59
60 def __output(cmd):
61     f = os.popen(cmd, 'r')
62     string = f.readline().strip()
63     if f.close():
64         print >> sys.stderr, 'Error: failed to execute "%s"' % cmd
65         sys.exit(1)
66     return string
67
68 def __checkout_files():
69     """Check out the files passed as arguments
70     """
71     global orig, src1, src2
72
73     if orig_hash:
74         orig = '%s.older' % path
75         tmp = __output('git-unpack-file %s' % orig_hash)
76         os.chmod(tmp, int(orig_mode, 8))
77         os.rename(tmp, orig)
78     if file1_hash:
79         src1 = '%s.local' % path
80         tmp = __output('git-unpack-file %s' % file1_hash)
81         os.chmod(tmp, int(file1_mode, 8))
82         os.rename(tmp, src1)
83     if file2_hash:
84         src2 = '%s.remote' % path
85         tmp = __output('git-unpack-file %s' % file2_hash)
86         os.chmod(tmp, int(file2_mode, 8))
87         os.rename(tmp, src2)
88
89 def __remove_files():
90     """Remove any temporary files
91     """
92     if orig_hash:
93         os.remove(orig)
94     if file1_hash:
95         os.remove(src1)
96     if file2_hash:
97         os.remove(src2)
98     pass
99
100 def __conflict():
101     """Write the conflict file for the 'path' variable and exit
102     """
103     append_string(os.path.join(base_dir, 'conflicts'), path)
104     sys.exit(1)
105
106
107 #   $1 - original file SHA1 (or empty)
108 #   $2 - file in branch1 SHA1 (or empty)
109 #   $3 - file in branch2 SHA1 (or empty)
110 #   $4 - pathname in repository
111 #   $5 - orignal file mode (or empty)
112 #   $6 - file in branch1 mode (or empty)
113 #   $7 - file in branch2 mode (or empty)
114 #print 'gitmerge.py "%s" "%s" "%s" "%s" "%s" "%s" "%s"' % tuple(sys.argv[1:8])
115 orig_hash, file1_hash, file2_hash, path, orig_mode, file1_mode, file2_mode = \
116            [__str2none(x) for x in sys.argv[1:8]]
117
118
119 #
120 # Main algorithm
121 #
122 __checkout_files()
123
124 # file exists in origin
125 if orig_hash:
126     # modified in both
127     if file1_hash and file2_hash:
128         # if modes are the same (git-read-tree probably dealed with it)
129         if file1_hash == file2_hash:
130             if os.system('git-update-cache --cacheinfo %s %s %s'
131                          % (file1_mode, file1_hash, path)) != 0:
132                 print >> sys.stderr, 'Error: git-update-cache failed'
133                 __conflict()
134             if os.system('git-checkout-cache -u -f -- %s' % path):
135                 print >> sys.stderr, 'Error: git-checkout-cache failed'
136                 __conflict()
137             if file1_mode != file2_mode:
138                 print >> sys.stderr, \
139                       'Error: File added in both, permissions conflict'
140                 __conflict()
141         # 3-way merge
142         else:
143             merge_ok = os.system(merger % {'branch1': src1,
144                                            'ancestor': orig,
145                                            'branch2': src2,
146                                            'output': path }) == 0
147
148             if merge_ok:
149                 os.system('git-update-cache %s' % path)
150                 __remove_files()
151                 sys.exit(0)
152             else:
153                 print >> sys.stderr, \
154                       'Error: three-way merge tool failed for file "%s"' % path
155                 # reset the cache to the first branch
156                 os.system('git-update-cache --cacheinfo %s %s %s'
157                           % (file1_mode, file1_hash, path))
158                 if keeporig != 'yes':
159                     __remove_files()
160                 __conflict()
161     # file deleted in both or deleted in one and unchanged in the other
162     elif not (file1_hash or file2_hash) \
163            or file1_hash == orig_hash or file2_hash == orig_hash:
164         if os.path.exists(path):
165             os.remove(path)
166         __remove_files()
167         sys.exit(os.system('git-update-cache --remove %s' % path))
168 # file does not exist in origin
169 else:
170     # file added in both
171     if file1_hash and file2_hash:
172         # files are the same
173         if file1_hash == file2_hash:
174             if os.system('git-update-cache --add --cacheinfo %s %s %s'
175                          % (file1_mode, file1_hash, path)) != 0:
176                 print >> sys.stderr, 'Error: git-update-cache failed'
177                 __conflict()
178             if os.system('git-checkout-cache -u -f -- %s' % path):
179                 print >> sys.stderr, 'Error: git-checkout-cache failed'
180                 __conflict()
181             if file1_mode != file2_mode:
182                 print >> sys.stderr, \
183                       'Error: File "s" added in both, permissions conflict' \
184                       % path
185                 __conflict()
186         # files are different
187         else:
188             print >> sys.stderr, \
189                   'Error: File "%s" added in branches but different' % path
190             __conflict()
191     # file added in one
192     elif file1_hash or file2_hash:
193         if file1_hash:
194             mode = file1_mode
195             obj = file1_hash
196         else:
197             mode = file2_mode
198             obj = file2_hash
199         if os.system('git-update-cache --add --cacheinfo %s %s %s'
200                      % (mode, obj, path)) != 0:
201             print >> sys.stderr, 'Error: git-update-cache failed'
202             __conflict()
203         __remove_files()
204         sys.exit(os.system('git-checkout-cache -u -f -- %s' % path))
205
206 # Un-handled case
207 print >> sys.stderr, 'Error: Un-handled merge conflict'
208 print >> sys.stderr, 'gitmerge.py "%s" "%s" "%s" "%s" "%s" "%s" "%s"' \
209       % tuple(sys.argv[1:8])
210 __conflict()