mirror of
https://github.com/openSUSE/osc.git
synced 2025-03-03 14:42:11 +01:00
- add diff3 merge support. Locally modified files are merged with upstream changes
if possible, and go into Conflict state if that fails. - add 'resolved' command to be used after manual merging.
This commit is contained in:
parent
2c37f7287d
commit
211b197b26
6
NEWS
6
NEWS
@ -1,7 +1,11 @@
|
|||||||
0.4:
|
0.4:
|
||||||
- allow 'up' inside a project directory (will automatically pull in all new
|
- allow 'up' inside a project directory (will automatically pull in all new
|
||||||
packages)
|
packages). (For past checkouts, you may need to put the project name into
|
||||||
|
$prjdir/.osc/_project yourself).
|
||||||
- checkout: preserve mtimes
|
- checkout: preserve mtimes
|
||||||
|
- add diff3 merge support. Locally modified files are merged with upstream changes
|
||||||
|
if possible, and go into Conflict state if that fails.
|
||||||
|
- add 'resolved' command to be used after manual merging.
|
||||||
|
|
||||||
|
|
||||||
0.3:
|
0.3:
|
||||||
|
3
README
3
README
@ -1,8 +1,5 @@
|
|||||||
osc -- opensuse-commander with svn like handling
|
osc -- opensuse-commander with svn like handling
|
||||||
|
|
||||||
>>> BUG: at the moment, 'up' overwrites files with
|
|
||||||
local modifications.
|
|
||||||
|
|
||||||
|
|
||||||
Please send patches to poeml@suse.de, or work directly on
|
Please send patches to poeml@suse.de, or work directly on
|
||||||
https://forgesvn1.novell.com/svn/opensuse/trunk/buildservice/src/clientlib/python/osc/
|
https://forgesvn1.novell.com/svn/opensuse/trunk/buildservice/src/clientlib/python/osc/
|
||||||
|
2
TODO
2
TODO
@ -2,10 +2,8 @@
|
|||||||
- implement 'mv' command
|
- implement 'mv' command
|
||||||
- implement 'trigger-rebuild'
|
- implement 'trigger-rebuild'
|
||||||
|
|
||||||
update: handle local modifications
|
|
||||||
|
|
||||||
checkin:
|
checkin:
|
||||||
- fix argument handling
|
|
||||||
- handle error if PUT fails, so the change is not committed to
|
- handle error if PUT fails, so the change is not committed to
|
||||||
localmeta
|
localmeta
|
||||||
|
|
||||||
|
@ -52,7 +52,8 @@ def main():
|
|||||||
for p in pacs:
|
for p in pacs:
|
||||||
if p.todo == []:
|
if p.todo == []:
|
||||||
for i in p.filenamelist:
|
for i in p.filenamelist:
|
||||||
if p.status(i) == 'M':
|
s = p.status(i)
|
||||||
|
if s == 'M' or s == 'C':
|
||||||
p.todo.append(i)
|
p.todo.append(i)
|
||||||
|
|
||||||
d = []
|
d = []
|
||||||
@ -210,8 +211,12 @@ def main():
|
|||||||
|
|
||||||
for p in pacs:
|
for p in pacs:
|
||||||
|
|
||||||
# save filelist before replacing the meta file
|
# save filelist and (modified) status before replacing the meta file
|
||||||
saved_filenames = p.filenamelist
|
saved_filenames = p.filenamelist
|
||||||
|
saved_modifiedfiles = []
|
||||||
|
for i in p.filenamelist:
|
||||||
|
if p.status(i) == 'M':
|
||||||
|
saved_modifiedfiles.append(i)
|
||||||
p.update_filesmeta()
|
p.update_filesmeta()
|
||||||
p = Package(p.dir)
|
p = Package(p.dir)
|
||||||
|
|
||||||
@ -231,10 +236,13 @@ def main():
|
|||||||
for filename in p.filenamelist:
|
for filename in p.filenamelist:
|
||||||
|
|
||||||
state = p.status(filename)
|
state = p.status(filename)
|
||||||
if state == 'M':
|
if state == 'M' and filename in saved_modifiedfiles:
|
||||||
print 'file %s is locally modified... fixme' % filename
|
print 'merging'
|
||||||
|
status_after_merge = p.mergefile(filename)
|
||||||
|
print statfrmt(status_after_merge, filename)
|
||||||
|
elif state == 'M':
|
||||||
p.updatefile(filename)
|
p.updatefile(filename)
|
||||||
print statfrmt('U', filename)
|
print statfrmt('M', filename)
|
||||||
elif state == '!':
|
elif state == '!':
|
||||||
p.updatefile(filename)
|
p.updatefile(filename)
|
||||||
print 'Restored \'%s\'' % filename
|
print 'Restored \'%s\'' % filename
|
||||||
@ -247,7 +255,8 @@ def main():
|
|||||||
|
|
||||||
p.update_pacmeta()
|
p.update_pacmeta()
|
||||||
|
|
||||||
print ljust(p.name, 45), 'At revision %s.' % p.rev
|
#print ljust(p.name, 45), 'At revision %s.' % p.rev
|
||||||
|
print 'At revision %s.' % p.rev
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -274,6 +283,20 @@ def main():
|
|||||||
print statfrmt('D', filename)
|
print statfrmt('D', filename)
|
||||||
|
|
||||||
|
|
||||||
|
elif cmd == 'resolved':
|
||||||
|
if len(sys.argv) < 3:
|
||||||
|
print '%s requires at least one argument' % cmd
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
args = parseargs()
|
||||||
|
pacs = findpacs(args)
|
||||||
|
|
||||||
|
for p in pacs:
|
||||||
|
|
||||||
|
for filename in p.todo:
|
||||||
|
print "Resolved conflicted state of '%s'" % filename
|
||||||
|
p.clear_from_conflictlist(filename)
|
||||||
|
|
||||||
|
|
||||||
elif cmd == 'id':
|
elif cmd == 'id':
|
||||||
print ''.join(get_user_id(sys.argv[2]))
|
print ''.join(get_user_id(sys.argv[2]))
|
||||||
|
93
osc/core.py
93
osc/core.py
@ -13,7 +13,6 @@ import urllib2
|
|||||||
from urlparse import urlunsplit
|
from urlparse import urlunsplit
|
||||||
import cElementTree as ET
|
import cElementTree as ET
|
||||||
from cStringIO import StringIO
|
from cStringIO import StringIO
|
||||||
from string import ljust
|
|
||||||
|
|
||||||
|
|
||||||
from xml.dom.ext.reader import Sax2
|
from xml.dom.ext.reader import Sax2
|
||||||
@ -102,6 +101,12 @@ class Package:
|
|||||||
self.filenamelist = []
|
self.filenamelist = []
|
||||||
self.filelist = []
|
self.filelist = []
|
||||||
for node in files_tree_root.findall('entry'):
|
for node in files_tree_root.findall('entry'):
|
||||||
|
try:
|
||||||
|
int(node.get('size'))
|
||||||
|
except:
|
||||||
|
print 'old _files metadata found.'
|
||||||
|
print 'run \'osc up\' after manually removing all "entry" lines from .osc/_files to upgrade.'
|
||||||
|
sys.exit(1)
|
||||||
f = File(node.get('name'),
|
f = File(node.get('name'),
|
||||||
node.get('md5'),
|
node.get('md5'),
|
||||||
int(node.get('size')),
|
int(node.get('size')),
|
||||||
@ -110,6 +115,7 @@ class Package:
|
|||||||
self.filenamelist.append(f.name)
|
self.filenamelist.append(f.name)
|
||||||
|
|
||||||
self.to_be_deleted = read_tobedeleted(self.dir)
|
self.to_be_deleted = read_tobedeleted(self.dir)
|
||||||
|
self.in_conflict = read_inconflict(self.dir)
|
||||||
|
|
||||||
self.todo = []
|
self.todo = []
|
||||||
self.todo_send = []
|
self.todo_send = []
|
||||||
@ -139,12 +145,45 @@ class Package:
|
|||||||
if n not in self.to_be_deleted:
|
if n not in self.to_be_deleted:
|
||||||
self.to_be_deleted.append(n)
|
self.to_be_deleted.append(n)
|
||||||
|
|
||||||
|
def put_on_conflictlist(self, n):
|
||||||
|
if n not in self.in_conflict:
|
||||||
|
self.in_conflict.append(n)
|
||||||
|
|
||||||
|
def clear_from_conflictlist(self, n):
|
||||||
|
"""delete an entry from the file, and remove the file if it would be empty"""
|
||||||
|
if n in self.in_conflict:
|
||||||
|
|
||||||
|
filename = os.path.join(self.dir, n)
|
||||||
|
storefilename = os.path.join(self.storedir, n)
|
||||||
|
myfilename = os.path.join(self.dir, n + '.mine')
|
||||||
|
upfilename = os.path.join(self.dir, n + '.r' + self.rev)
|
||||||
|
|
||||||
|
os.unlink(myfilename)
|
||||||
|
os.rename(upfilename, storefilename)
|
||||||
|
|
||||||
|
self.in_conflict.remove(n)
|
||||||
|
|
||||||
|
self.write_deletelist()
|
||||||
|
|
||||||
def write_deletelist(self):
|
def write_deletelist(self):
|
||||||
fname = os.path.join(self.storedir, '_to_be_deleted')
|
if len(self.to_be_deleted) == 0:
|
||||||
f = open(fname, 'w')
|
os.unlink(os.path.join(self.storedir, '_to_be_deleted'))
|
||||||
f.write('\n'.join(self.to_be_deleted))
|
else:
|
||||||
f.write('\n')
|
fname = os.path.join(self.storedir, '_to_be_deleted')
|
||||||
f.close()
|
f = open(fname, 'w')
|
||||||
|
f.write('\n'.join(self.to_be_deleted))
|
||||||
|
f.write('\n')
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
def write_conflictlist(self):
|
||||||
|
if len(self.in_conflict) == 0:
|
||||||
|
os.unlink(os.path.join(self.storedir, '_in_conflict'))
|
||||||
|
else:
|
||||||
|
fname = os.path.join(self.storedir, '_in_conflict')
|
||||||
|
f = open(fname, 'w')
|
||||||
|
f.write('\n'.join(self.in_conflict))
|
||||||
|
f.write('\n')
|
||||||
|
f.close()
|
||||||
|
|
||||||
def updatefile(self, n):
|
def updatefile(self, n):
|
||||||
filename = os.path.join(self.dir, n)
|
filename = os.path.join(self.dir, n)
|
||||||
@ -157,6 +196,31 @@ class Package:
|
|||||||
copy_file(filename, storefilename)
|
copy_file(filename, storefilename)
|
||||||
os.utime(storefilename, (-1, mtime))
|
os.utime(storefilename, (-1, mtime))
|
||||||
|
|
||||||
|
def mergefile(self, n):
|
||||||
|
filename = os.path.join(self.dir, n)
|
||||||
|
storefilename = os.path.join(self.storedir, n)
|
||||||
|
myfilename = os.path.join(self.dir, n + '.mine')
|
||||||
|
upfilename = os.path.join(self.dir, n + '.r' + self.rev)
|
||||||
|
os.rename(filename, myfilename)
|
||||||
|
|
||||||
|
get_source_file(self.prjname, self.name, n, targetfilename=upfilename)
|
||||||
|
|
||||||
|
ret = os.system('cd %s; diff3 -m -E %s %s %s > %s' \
|
||||||
|
% (self.dir, myfilename, storefilename, upfilename, filename))
|
||||||
|
if ret == 0:
|
||||||
|
# merge was successful... clean up
|
||||||
|
os.rename(upfilename, filename)
|
||||||
|
copy_file(filename, storefilename)
|
||||||
|
os.unlink(myfilename)
|
||||||
|
return 'M'
|
||||||
|
else:
|
||||||
|
# unsuccessful merge
|
||||||
|
self.in_conflict.append(n)
|
||||||
|
self.write_conflictlist()
|
||||||
|
return 'C'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def update_filesmeta(self):
|
def update_filesmeta(self):
|
||||||
meta = ''.join(show_files_meta(self.prjname, self.name))
|
meta = ''.join(show_files_meta(self.prjname, self.name))
|
||||||
f = open(os.path.join(self.storedir, '_files'), 'w')
|
f = open(os.path.join(self.storedir, '_files'), 'w')
|
||||||
@ -182,7 +246,8 @@ class Package:
|
|||||||
exists exists in _files
|
exists exists in _files
|
||||||
|
|
||||||
x x - 'A'
|
x x - 'A'
|
||||||
x x x 'M', if digest differs, else ' '
|
x x x ' ' if digest differs: 'M'
|
||||||
|
and if in conflicts file: 'C'
|
||||||
x - - '?'
|
x - - '?'
|
||||||
x - x 'D' and listed in _to_be_deleted
|
x - x 'D' and listed in _to_be_deleted
|
||||||
- x x '!'
|
- x x '!'
|
||||||
@ -207,6 +272,8 @@ class Package:
|
|||||||
state = 'D'
|
state = 'D'
|
||||||
elif n in self.to_be_deleted:
|
elif n in self.to_be_deleted:
|
||||||
state = 'D'
|
state = 'D'
|
||||||
|
elif n in self.in_conflict:
|
||||||
|
state = 'C'
|
||||||
elif exists and exists_in_store and known_by_meta:
|
elif exists and exists_in_store and known_by_meta:
|
||||||
#print self.findfilebyname(n)
|
#print self.findfilebyname(n)
|
||||||
if dgst(os.path.join(self.dir, n)) != self.findfilebyname(n).md5:
|
if dgst(os.path.join(self.dir, n)) != self.findfilebyname(n).md5:
|
||||||
@ -295,6 +362,18 @@ def read_tobedeleted(dir):
|
|||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def read_inconflict(dir):
|
||||||
|
r = []
|
||||||
|
fname = os.path.join(dir, store, '_in_conflict')
|
||||||
|
|
||||||
|
if os.path.exists(fname):
|
||||||
|
|
||||||
|
for i in open(fname, 'r').readlines():
|
||||||
|
r.append(i.strip())
|
||||||
|
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
def parseargs():
|
def parseargs():
|
||||||
if len(sys.argv) > 2:
|
if len(sys.argv) > 2:
|
||||||
args = sys.argv[2:]
|
args = sys.argv[2:]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user