@log_calls
  def diff(self, scope, opt_rev=None, path_list=None):
    '''
    Diff operation.

    Possible scenarios:
      opt_rev not provided, and there are no uncommitted change
        - do nothing
      opt_rev not provided, and there are uncommitted changes
        - show the uncommitted changes
      opt_rev provided, and there are no uncommitted changes
        - show the changes between the rev and current checked out rev
      opt_rev provided, and there are uncommitted changes
        - show the changes between the rev and uncommitted changes
      opt_rev is a range
        - show the changes between the revs (regardless the uncommitted changes)
    '''

    changelist = self.status(scope)
    cur_rev = self.get_checkout_rev(scope)
    base_rev = None
    trgt_rev = None # trgt_rev = None means uncommitted local changes
    base_msg = ""
    trgt_msg = ""

    if opt_rev:
      (base_rev, trgt_rev) = parse_rev_range(opt_rev)
      # check if starting rev is correct
      if not base_rev:
        raise InvalidArgument("invalid revision range: '%s', the starting revision is mandatory for zinc diff\n" % opt_rev)

      base_msg = "--rev '%s'" % base_rev
      if trgt_rev:
        trgt_msg = "--rev '%s'" % trgt_rev
      else:
        # choose checkedout rev unless there are uncommitted local changes
        if changelist.is_empty():
          trgt_msg = "checkedout revision"
          trgt_rev = cur_rev
        else:
          trgt_msg = "uncommitted local changes"

      # Get base manifest
      base_mf = self.repo.get_manifest(scope, base_rev)

      # Get target manifest
      trgt_mf = self.repo.get_manifest(scope, (trgt_rev if trgt_rev else cur_rev))

      # Find out what files we need to update.
      if trgt_rev:
        changelist = changelist_for_items(base_mf.items, trgt_mf.items)
      else:
        changelist = changelist_for_items(base_mf.items, trgt_mf.items).union(changelist)

    elif not changelist.is_empty():
      base_msg = "checkedout revision"
      trgt_msg = "uncommited local changes"
      base_rev = cur_rev

    if base_rev:
      #extract only changes in path_list
      if path_list:
        tmp_changelist = Changelist()
        for path in path_list:
          tmp_changelist.update(path, Changelist.Status.MODIFIED)
        changelist = changelist.intersect(tmp_changelist)

    if not changelist.is_empty():
      sys.stdout.write("diff --zinc between %s and %s\n" % (base_msg, trgt_msg))
      assert scope
      sys.stdout.write("scope: %s\n" % scope)
      if path_list:
        sys.stdout.write("paths: %s\n" % path_list)

    def _do_print_diff(path_a, path_b):
      @contextmanager
      def _get_real_path_and_label(path, rev=None):
        if not path:
          yield (EMPTY_FILEPATH, EMPTY_FILEPATH)
        else:
          label = join_path(scope, path)
          if rev:
            items = self.repo.list(scope, path, rev, recursive=False)
            assert len(items) == 1
            store_path = join_path(self.repo.store_path_dir(scope), items[0].store_path)
            (cache_path, fp) = self.repo.store.cache(store_path)
            if not cache_path:
              raise InvalidOperation("Cannot find chached file for '%s'" % store_path)
            with decompressed_local_file(cache_path, items[0].scheme) as decompressed_path:
              yield (decompressed_path, label)
          else:
            full_path = join_path(self.work_dir, scope, path)
            yield (full_path, label)

      # TODO from python 2.7 multiple context expresssions are supported so we do not have to nest like this
      with _get_real_path_and_label(path_a, base_rev) as (base_path, base_label):
        with _get_real_path_and_label(path_b, trgt_rev) as (trgt_path, trgt_label):
          print_diff(base_path, trgt_path, base_label, trgt_label)

    for path in changelist.add_paths:
      _do_print_diff(None, path)

    for path in changelist.rm_paths:
      _do_print_diff(path, None)

    for path in changelist.mod_paths:
      _do_print_diff(path, path)
dataset/ETHPy150Open bloomreach/zinc/zinc.py/WorkingDir.diff/original