From: Karl Hasselström Date: Tue, 8 Jul 2008 18:24:43 +0000 (+0200) Subject: Merge branch 'stable' X-Git-Tag: v0.15-rc1~203 X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~mdw/git/stgit/commitdiff_plain/7a6a45b5f1379466bd23b33a7feed47d0b8331b7?hp=-c Merge branch 'stable' Conflicts: stgit/commands/status.py --- 7a6a45b5f1379466bd23b33a7feed47d0b8331b7 diff --combined stgit/commands/status.py index a5b2f88,94d0b57..a84ff6c --- a/stgit/commands/status.py +++ b/stgit/commands/status.py @@@ -61,18 -61,16 +61,15 @@@ options = [make_option('-m', '--modifie action = 'store_true'), make_option('--reset', help = 'reset the current tree changes', - action = 'store_true') - ] + make_diff_opts_option() + action = 'store_true')] - def status(files, modified, new, deleted, conflict, unknown, noexclude, - diff_flags): -def status(files = None, modified = False, new = False, deleted = False, - conflict = False, unknown = False, noexclude = False): ++def status(files, modified, new, deleted, conflict, unknown, noexclude): """Show the tree status """ cache_files = git.tree_status(files, unknown = (not files), - noexclude = noexclude, - diff_flags = diff_flags) + noexclude = noexclude) filtered = (modified or new or deleted or conflict or unknown) if filtered: @@@ -107,13 -105,12 +104,12 @@@ def func(parser, options, args) if options.reset: if args: - for f in args: - resolved(f) + conflicts = git.get_conflicts() + git.resolved([fn for fn in args if fn in conflicts]) git.reset(args) else: resolved_all() git.reset() else: status(args, options.modified, options.new, options.deleted, - options.conflict, options.unknown, options.noexclude, - options.diff_flags) + options.conflict, options.unknown, options.noexclude) diff --combined stgit/git.py index 0955be7,35579d4..ee31ecd --- a/stgit/git.py +++ b/stgit/git.py @@@ -43,6 -43,7 +43,6 @@@ class GRun(Run) """ Run.__init__(self, 'git', *cmd) - # # Classes # @@@ -153,12 -154,14 +153,12 @@@ def get_commit(id_hash) def get_conflicts(): """Return the list of file conflicts """ - conflicts_file = os.path.join(basedir.get(), 'conflicts') - if os.path.isfile(conflicts_file): - f = file(conflicts_file) - names = [line.strip() for line in f.readlines()] - f.close() - return names - else: - return None + names = set() + for line in GRun('ls-files', '-z', '--unmerged' + ).raw_output().split('\0')[:-1]: + stat, path = line.split('\t', 1) + names.add(path) + return list(names) def exclude_files(): files = [os.path.join(basedir.get(), 'info', 'exclude')] @@@ -182,15 -185,16 +182,18 @@@ def ls_files(files, tree = 'HEAD', full args.append('--') args.extend(files) try: - return GRun('ls-files', '--error-unmatch', *args).output_lines() + # use a set to avoid file names duplication due to different stages + fileset = set(GRun('ls-files', '--error-unmatch', *args).output_lines()) except GitRunException: # just hide the details of the 'git ls-files' command we use raise GitException, \ 'Some of the given paths are either missing or not known to GIT' + return list(fileset) def parse_git_ls(output): + """Parse the output of git diff-index, diff-files, etc. Doesn't handle + rename/copy output, so don't feed it output generated with the -M + or -C flags.""" t = None for line in output.split('\0'): if not line: @@@ -204,7 -208,7 +207,7 @@@ t = None def tree_status(files = None, tree_id = 'HEAD', unknown = False, - noexclude = True, verbose = False, diff_flags = []): + noexclude = True, verbose = False): """Get the status of all changed files, or of a selected set of files. Returns a list of pairs - (status, filename). @@@ -240,6 -244,8 +243,6 @@@ # conflicted files conflicts = get_conflicts() - if not conflicts: - conflicts = [] cache_files += [('C', filename) for filename in conflicts if not files or filename in files] reported_files = set(conflicts) @@@ -249,7 -255,7 +252,7 @@@ # specified when calling the function (i.e. report all files) or # files were specified but already found in the previous step if not files or files_left: - args = diff_flags + [tree_id] + args = [tree_id] if files_left: args += ['--'] + files_left for t, fn in parse_git_ls(GRun('diff-index', '-z', *args).raw_output()): @@@ -265,7 -271,7 +268,7 @@@ # function (i.e. report all files) or files were specified but # already found in the previous step if not files or files_left: - args = list(diff_flags) + args = [] if files_left: args += ['--'] + files_left for t, fn in parse_git_ls(GRun('diff-files', '-z', *args).raw_output()): @@@ -469,6 -475,109 +472,6 @@@ def rename_branch(from_name, to_name) and os.path.exists(os.path.join(reflog_dir, from_name)): rename(reflog_dir, from_name, to_name) -def add(names): - """Add the files or recursively add the directory contents - """ - # generate the file list - files = [] - for i in names: - if not os.path.exists(i): - raise GitException, 'Unknown file or directory: %s' % i - - if os.path.isdir(i): - # recursive search. We only add files - for root, dirs, local_files in os.walk(i): - for name in [os.path.join(root, f) for f in local_files]: - if os.path.isfile(name): - files.append(os.path.normpath(name)) - elif os.path.isfile(i): - files.append(os.path.normpath(i)) - else: - raise GitException, '%s is not a file or directory' % i - - if files: - try: - GRun('update-index', '--add', '--').xargs(files) - except GitRunException: - raise GitException, 'Unable to add file' - -def __copy_single(source, target, target2=''): - """Copy file or dir named 'source' to name target+target2""" - - # "source" (file or dir) must match one or more git-controlled file - realfiles = GRun('ls-files', source).output_lines() - if len(realfiles) == 0: - raise GitException, '"%s" matches no git-controled files' % source - - if os.path.isdir(source): - # physically copy the files, and record them to add them in one run - newfiles = [] - re_string='^'+source+'/(.*)$' - prefix_regexp = re.compile(re_string) - for f in [f.strip() for f in realfiles]: - m = prefix_regexp.match(f) - if not m: - raise Exception, '"%s" does not match "%s"' % (f, re_string) - newname = target+target2+'/'+m.group(1) - if not os.path.exists(os.path.dirname(newname)): - os.makedirs(os.path.dirname(newname)) - copyfile(f, newname) - newfiles.append(newname) - - add(newfiles) - else: # files, symlinks, ... - newname = target+target2 - copyfile(source, newname) - add([newname]) - - -def copy(filespecs, target): - if os.path.isdir(target): - # target is a directory: copy each entry on the command line, - # with the same name, into the target - target = target.rstrip('/') - - # first, check that none of the children of the target - # matching the command line aleady exist - for filespec in filespecs: - entry = target+ '/' + os.path.basename(filespec.rstrip('/')) - if os.path.exists(entry): - raise GitException, 'Target "%s" already exists' % entry - - for filespec in filespecs: - filespec = filespec.rstrip('/') - basename = '/' + os.path.basename(filespec) - __copy_single(filespec, target, basename) - - elif os.path.exists(target): - raise GitException, 'Target "%s" exists but is not a directory' % target - elif len(filespecs) != 1: - raise GitException, 'Cannot copy more than one file to non-directory' - - else: - # at this point: len(filespecs)==1 and target does not exist - - # check target directory - targetdir = os.path.dirname(target) - if targetdir != '' and not os.path.isdir(targetdir): - raise GitException, 'Target directory "%s" does not exist' % targetdir - - __copy_single(filespecs[0].rstrip('/'), target) - - -def rm(files, force = False): - """Remove a file from the repository - """ - if not force: - for f in files: - if os.path.exists(f): - raise GitException, '%s exists. Remove it first' %f - if files: - GRun('update-index', '--remove', '--').xargs(files) - else: - if files: - GRun('update-index', '--force-remove', '--').xargs(files) - # Persons caching __user = None __author = None @@@ -615,33 -724,77 +618,33 @@@ def apply_diff(rev1, rev2, check_index return True -def merge(base, head1, head2, recursive = False): +stages_re = re.compile('^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S) + +def merge_recursive(base, head1, head2): """Perform a 3-way merge between base, head1 and head2 into the local tree """ refresh_index() - - err_output = None - if recursive: - # this operation tracks renames but it is slower (used in - # general when pushing or picking patches) - try: - # discard output to mask the verbose prints of the tool - GRun('merge-recursive', base, '--', head1, head2 - ).discard_output() - except GitRunException, ex: - err_output = str(ex) - pass - else: - # the fast case where we don't track renames (used when the - # distance between base and heads is small, i.e. folding or - # synchronising patches) - try: - GRun('read-tree', '-u', '-m', '--aggressive', - base, head1, head2).run() - except GitRunException: - raise GitException, 'read-tree failed (local changes maybe?)' - - # check the index for unmerged entries - files = {} - stages_re = re.compile('^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S) - - for line in GRun('ls-files', '--unmerged', '--stage', '-z' - ).raw_output().split('\0'): - if not line: - continue - - mode, hash, stage, path = stages_re.findall(line)[0] - - if not path in files: - files[path] = {} - files[path]['1'] = ('', '') - files[path]['2'] = ('', '') - files[path]['3'] = ('', '') - - files[path][stage] = (mode, hash) - - if err_output and not files: - # if no unmerged files, there was probably a different type of - # error and we have to abort the merge - raise GitException, err_output - - # merge the unmerged files - errors = False - for path in files: - # remove additional files that might be generated for some - # newer versions of GIT - for suffix in [base, head1, head2]: - if not suffix: - continue - fname = path + '~' + suffix - if os.path.exists(fname): - os.remove(fname) - - stages = files[path] - if gitmergeonefile.merge(stages['1'][1], stages['2'][1], - stages['3'][1], path, stages['1'][0], - stages['2'][0], stages['3'][0]) != 0: - errors = True - - if errors: - raise GitException, 'GIT index merging failed (possible conflicts)' + p = GRun('merge-recursive', base, '--', head1, head2).env( + { 'GITHEAD_%s' % base: 'ancestor', + 'GITHEAD_%s' % head1: 'current', + 'GITHEAD_%s' % head2: 'patched'}).returns([0, 1]) + output = p.output_lines() + if p.exitcode: + # There were conflicts + conflicts = [l.strip() for l in output if l.startswith('CONFLICT')] + out.info(*conflicts) + + # try the interactive merge or stage checkout (if enabled) + for filename in get_conflicts(): + if (gitmergeonefile.merge(filename)): + # interactive merge succeeded + resolved([filename]) + + # any conflicts left unsolved? + cn = len(get_conflicts()) + if cn: + raise GitException, "%d conflict(s)" % cn def diff(files = None, rev1 = 'HEAD', rev2 = None, diff_flags = [], binary = True): @@@ -666,9 -819,12 +669,9 @@@ else: return '' -# TODO: take another parameter representing a diff string as we -# usually invoke git.diff() form the calling functions -def diffstat(files = None, rev1 = 'HEAD', rev2 = None): - """Return the diffstat between rev1 and rev2.""" - return GRun('apply', '--stat', '--summary' - ).raw_input(diff(files, rev1, rev2)).raw_output() +def diffstat(diff): + """Return the diffstat of the supplied diff.""" + return GRun('apply', '--stat', '--summary').raw_input(diff).raw_output() def files(rev1, rev2, diff_flags = []): """Return the files modified between rev1 and rev2 @@@ -748,17 -904,6 +751,17 @@@ def reset(files = None, tree_id = None if not files: __set_head(tree_id) +def resolved(filenames, reset = None): + if reset: + stage = {'ancestor': 1, 'current': 2, 'patched': 3}[reset] + GRun('checkout-index', '--no-create', '--stage=%d' % stage, + '--stdin', '-z').input_nulterm(filenames).no_output() + GRun('update-index', '--add', '--').xargs(filenames) + for filename in filenames: + gitmergeonefile.clean_up(filename) + # update the access and modificatied times + os.utime(filename, None) + def fetch(repository = 'origin', refspec = None): """Fetches changes from the remote repository, using 'git fetch' by default. @@@ -853,7 -998,7 +856,7 @@@ def apply_patch(filename = None, diff top = commit(message = 'temporary commit used for applying a patch', parents = [base]) switch(orig_head) - merge(base, orig_head, top) + merge_recursive(base, orig_head, top) def clone(repository, local_dir): """Clone a remote repository. At the moment, just use the diff --combined t/t0002-status.sh index 4364709,a030739..5e1e8ca --- a/t/t0002-status.sh +++ b/t/t0002-status.sh @@@ -20,7 -20,7 +20,7 @@@ cat > expected.txt < output.txt && - diff -u expected.txt output.txt + test_cmp expected.txt output.txt ' cat > expected.txt < output.txt && - diff -u expected.txt output.txt + test_cmp expected.txt output.txt ' rm -f foo @@@ -38,7 -38,7 +38,7 @@@ EO test_expect_success 'Status with an empty directory' ' mkdir foo && stg status > output.txt && - diff -u expected.txt output.txt + test_cmp expected.txt output.txt ' cat > expected.txt < output.txt && - diff -u expected.txt output.txt + test_cmp expected.txt output.txt ' cat > expected.txt < output.txt && - diff -u expected.txt output.txt + test_cmp expected.txt output.txt ' cat > expected.txt < output.txt && - diff -u expected.txt output.txt + test_cmp expected.txt output.txt ' cat > expected.txt < output.txt && - diff -u expected.txt output.txt + test_cmp expected.txt output.txt ' cat > expected.txt <> foo/bar && stg status > output.txt && - diff -u expected.txt output.txt + test_cmp expected.txt output.txt ' cat > expected.txt < output.txt && - diff -u expected.txt output.txt + test_cmp expected.txt output.txt ' test_expect_success 'Add another file' ' echo lajbans > fie && - stg add fie && + git add fie && stg refresh ' @@@ -110,13 -110,12 +110,13 @@@ cat > expected.txt < output.txt && - diff -u expected.txt output.txt + test_cmp expected.txt output.txt ' cat > expected.txt < output.txt && - diff -u expected.txt output.txt + test_cmp expected.txt output.txt ' cat > expected.txt < output.txt && - diff -u expected.txt output.txt + test_cmp expected.txt output.txt ' cat > expected.txt < output.txt && - diff -u expected.txt output.txt + test_cmp expected.txt output.txt ' cat > expected.txt < output.txt && - diff -u expected.txt output.txt + test_cmp expected.txt output.txt ' cat > expected.txt < output.txt && - diff -u expected.txt output.txt + test_cmp expected.txt output.txt ' cat > expected.txt < output.txt && - diff -u expected.txt output.txt + test_cmp expected.txt output.txt ' + cat > expected.txt < output.txt && - diff -u expected.txt output.txt ++ test_cmp expected.txt output.txt + ' + test_done