chiark / gitweb /
Merge branch 'stable'
authorKarl Hasselström <kha@treskal.com>
Tue, 8 Jul 2008 18:24:43 +0000 (20:24 +0200)
committerKarl Hasselström <kha@treskal.com>
Tue, 8 Jul 2008 18:26:47 +0000 (20:26 +0200)
Conflicts:

stgit/commands/status.py

1  2 
stgit/commands/status.py
stgit/git.py
t/t0002-status.sh

diff --combined stgit/commands/status.py
index a5b2f8865c5a0cf147c498160db780eba0febca8,94d0b57ac0ef2fb75c6e2ce9750d2c5bebf8d7a2..a84ff6c6d33362a1f1ff13772eb3a25d2f3ce575
@@@ -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 0955be74b2c96af18df7e10824dba4be215dbebd,35579d4223f03f6160ce305681bd8c823cad8d1b..ee31ecd07a5a7d17d1a9bba364658e97c0cdaedc
@@@ -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:
              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).
  
  
      # 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)
      # 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()):
      # 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):
      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 436470943baed5e000eff4966cbde969faca652d,a0307394699caa485bf5cf8f8a550802dd750ec4..5e1e8cae3d286cc3394089374327763236239bf6
@@@ -20,7 -20,7 +20,7 @@@ cat > expected.txt <<EO
  EOF
  test_expect_success 'Run status on empty' '
      stg status > output.txt &&
 -    diff -u expected.txt output.txt
 +    test_cmp expected.txt output.txt
  '
  
  cat > expected.txt <<EOF
@@@ -29,7 -29,7 +29,7 @@@ EO
  test_expect_success 'Status with an untracked file' '
      touch foo &&
      stg status > 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 <<EOF
@@@ -47,16 -47,16 +47,16 @@@ EO
  test_expect_success 'Status with an untracked file in a subdir' '
      touch foo/bar &&
      stg status > output.txt &&
 -    diff -u expected.txt output.txt
 +    test_cmp expected.txt output.txt
  '
  
  cat > expected.txt <<EOF
  A foo/bar
  EOF
  test_expect_success 'Status with an added file' '
 -    stg add foo &&
 +    git add foo &&
      stg status > output.txt &&
 -    diff -u expected.txt output.txt
 +    test_cmp expected.txt output.txt
  '
  
  cat > expected.txt <<EOF
@@@ -64,7 -64,7 +64,7 @@@ foo/ba
  EOF
  test_expect_success 'Status with an added file and -n option' '
      stg status -n > output.txt &&
 -    diff -u expected.txt output.txt
 +    test_cmp expected.txt output.txt
  '
  
  cat > expected.txt <<EOF
@@@ -73,7 -73,7 +73,7 @@@ test_expect_success 'Status after refre
      stg new -m "first patch" &&
      stg refresh &&
      stg status > output.txt &&
 -    diff -u expected.txt output.txt
 +    test_cmp expected.txt output.txt
  '
  
  cat > expected.txt <<EOF
@@@ -82,7 -82,7 +82,7 @@@ EO
  test_expect_success 'Status after modification' '
      echo "wee" >> foo/bar &&
      stg status > output.txt &&
 -    diff -u expected.txt output.txt
 +    test_cmp expected.txt output.txt
  '
  
  cat > expected.txt <<EOF
@@@ -90,12 -90,12 +90,12 @@@ EO
  test_expect_success 'Status after refresh' '
      stg new -m "second patch" && stg refresh &&
      stg status > 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 <<EO
  ? foo/bar.ancestor
  ? foo/bar.current
  ? foo/bar.patched
 +A fie
  C foo/bar
  EOF
  test_expect_success 'Status after conflicting push' '
      ! stg push &&
      stg status > output.txt &&
 -    diff -u expected.txt output.txt
 +    test_cmp expected.txt output.txt
  '
  
  cat > expected.txt <<EOF
@@@ -124,7 -123,7 +124,7 @@@ C foo/ba
  EOF
  test_expect_success 'Status of file' '
      stg status foo/bar > output.txt &&
 -    diff -u expected.txt output.txt
 +    test_cmp expected.txt output.txt
  '
  
  cat > expected.txt <<EOF
@@@ -132,35 -131,32 +132,35 @@@ C foo/ba
  EOF
  test_expect_success 'Status of dir' '
      stg status foo > output.txt &&
 -    diff -u expected.txt output.txt
 +    test_cmp expected.txt output.txt
  '
  
  cat > expected.txt <<EOF
 +A fie
  EOF
  test_expect_success 'Status of other file' '
      stg status fie > output.txt &&
 -    diff -u expected.txt output.txt
 +    test_cmp expected.txt output.txt
  '
  
  cat > expected.txt <<EOF
 +A fie
  M foo/bar
  EOF
  test_expect_success 'Status after resolving the push' '
      stg resolved -a &&
      stg status > output.txt &&
 -    diff -u expected.txt output.txt
 +    test_cmp expected.txt output.txt
  '
  
  cat > expected.txt <<EOF
 +A fie
  D foo/bar
  EOF
  test_expect_success 'Status after deleting a file' '
      rm foo/bar &&
      stg status > output.txt &&
 -    diff -u expected.txt output.txt
 +    test_cmp expected.txt output.txt
  '
  
  cat > expected.txt <<EOF
@@@ -169,10 -165,21 +169,21 @@@ EO
  test_expect_success 'Status of disappeared newborn' '
      stg refresh &&
      touch foo/bar &&
 -    stg add foo/bar &&
 +    git add foo/bar &&
      rm foo/bar &&
      stg status > output.txt &&
 -    diff -u expected.txt output.txt
 +    test_cmp expected.txt output.txt
  '
  
 -    diff -u expected.txt output.txt
+ cat > expected.txt <<EOF
+ A fay
+ D fie
+ EOF
+ test_expect_success 'Status after renaming a file' '
+     git rm foo/bar &&
+     git mv fie fay &&
+     stg status > output.txt &&
++    test_cmp expected.txt output.txt
+ '
  test_done