Merge branch 'js/remote'
authorJunio C Hamano <gitster@pobox.com>
Wed, 12 Mar 2008 05:33:51 +0000 (22:33 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 12 Mar 2008 05:33:51 +0000 (22:33 -0700)
* js/remote:
"remote update": print remote name being fetched from
builtin remote rm: remove symbolic refs, too
remote: fix "update [group...]"
remote show: Clean up connection correctly if object fetch wasn't done
builtin-remote: prune remotes correctly that were added with --mirror
Make git-remote a builtin
Test "git remote show" and "git remote prune"
parseopt: add flag to stop on first non option
path-list: add functions to work with unsorted lists

Conflicts:

parse-options.c

1  2 
Makefile
contrib/examples/git-remote.perl
parse-options.c
parse-options.h
diff --combined Makefile
index 514738046f94abc9ab16bfebc3b0d3e0118b5a5f,f9ea96a101cdc489e73152a7ec47165991557d2f..e3eaa6a543e7c3eed8a72c00f8b5541524fc9508
+++ b/Makefile
@@@ -3,10 -3,6 +3,10 @@@ all:
  
  # Define V=1 to have a more verbose compile.
  #
 +# Define SNPRINTF_RETURNS_BOGUS if your are on a system which snprintf()
 +# or vsnprintf() return -1 instead of number of characters which would
 +# have been written to the final string if enough space had been available.
 +#
  # Define FREAD_READS_DIRECTORIES if your are on a system which succeeds
  # when attempting to read from an fopen'ed directory.
  #
@@@ -247,7 -243,7 +247,7 @@@ SCRIPT_SH = 
  SCRIPT_PERL = \
        git-add--interactive.perl \
        git-archimport.perl git-cvsimport.perl git-relink.perl \
-       git-cvsserver.perl git-remote.perl git-cvsexportcommit.perl \
+       git-cvsserver.perl git-cvsexportcommit.perl \
        git-send-email.perl git-svn.perl
  
  SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
@@@ -308,8 -304,7 +308,8 @@@ LIB_H = 
        run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \
        tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h \
        utf8.h reflog-walk.h patch-ids.h attr.h decorate.h progress.h \
 -      mailmap.h remote.h parse-options.h transport.h diffcore.h hash.h
 +      mailmap.h remote.h parse-options.h transport.h diffcore.h hash.h ll-merge.h fsck.h \
 +      pack-revindex.h
  
  DIFF_OBJS = \
        diff.o diff-lib.o diffcore-break.o diffcore-order.o \
@@@ -324,7 -319,7 +324,7 @@@ LIB_OBJS = 
        patch-ids.o \
        object.o pack-check.o pack-write.o patch-delta.o path.o pkt-line.o \
        sideband.o reachable.o reflog-walk.o \
 -      quote.o read-cache.o refs.o run-command.o dir.o object-refs.o \
 +      quote.o read-cache.o refs.o run-command.o dir.o \
        server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
        tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
        revision.o pager.o tree-walk.o xdiff-interface.o \
        color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \
        convert.o attr.o decorate.o progress.o mailmap.o symlinks.o remote.o \
        transport.o bundle.o walker.o parse-options.o ws.o archive.o branch.o \
 -      alias.o
 +      ll-merge.o alias.o fsck.o pack-revindex.o
  
  BUILTIN_OBJS = \
        builtin-add.o \
        builtin-push.o \
        builtin-read-tree.o \
        builtin-reflog.o \
+       builtin-remote.o \
        builtin-send-pack.o \
        builtin-config.o \
        builtin-rerere.o \
@@@ -482,7 -478,6 +483,7 @@@ ifeq ($(uname_S),FreeBSD
        NO_MEMMEM = YesPlease
        BASIC_CFLAGS += -I/usr/local/include
        BASIC_LDFLAGS += -L/usr/local/lib
 +      DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease
  endif
  ifeq ($(uname_S),OpenBSD)
        NO_STRCASESTR = YesPlease
@@@ -634,10 -629,6 +635,10 @@@ endi
  ifdef NO_C99_FORMAT
        BASIC_CFLAGS += -DNO_C99_FORMAT
  endif
 +ifdef SNPRINTF_RETURNS_BOGUS
 +      COMPAT_CFLAGS += -DSNPRINTF_RETURNS_BOGUS
 +      COMPAT_OBJS += compat/snprintf.o
 +endif
  ifdef FREAD_READS_DIRECTORIES
        COMPAT_CFLAGS += -DFREAD_READS_DIRECTORIES
        COMPAT_OBJS += compat/fopen.o
@@@ -756,9 -747,6 +757,9 @@@ ifdef THREADED_DELTA_SEARC
        EXTLIBS += -lpthread
        LIB_OBJS += thread-utils.o
  endif
 +ifdef DIR_HAS_BSD_GROUP_SEMANTICS
 +      COMPAT_CFLAGS += -DDIR_HAS_BSD_GROUP_SEMANTICS
 +endif
  
  ifeq ($(TCLTK_PATH),)
  NO_TCLTK=NoThanks
@@@ -825,7 -813,7 +826,7 @@@ export TAR INSTALL DESTDIR SHELL_PAT
  
  ### Build rules
  
 -all:: $(ALL_PROGRAMS) $(BUILT_INS) $(OTHER_PROGRAMS)
 +all:: $(ALL_PROGRAMS) $(BUILT_INS) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS
  ifneq (,$X)
        $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), $(RM) '$p';)
  endif
@@@ -1024,9 -1012,6 +1025,9 @@@ GIT-CFLAGS: .FORCE-GIT-CFLAG
                echo "$$FLAGS" >GIT-CFLAGS; \
              fi
  
 +GIT-BUILD-OPTIONS: .FORCE-GIT-BUILD-OPTIONS
 +      @echo SHELL_PATH=\''$(SHELL_PATH_SQ)'\' >$@
 +
  ### Detect Tck/Tk interpreter path changes
  ifndef NO_TCLTK
  TRACK_VARS = $(subst ','\'',-DTCLTK_PATH='$(TCLTK_PATH_SQ)')
@@@ -1182,11 -1167,10 +1183,11 @@@ ifndef NO_TCLT
        $(MAKE) -C gitk-git clean
        $(MAKE) -C git-gui clean
  endif
 -      $(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-GUI-VARS
 +      $(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-GUI-VARS GIT-BUILD-OPTIONS
  
  .PHONY: all install clean strip
  .PHONY: .FORCE-GIT-VERSION-FILE TAGS tags cscope .FORCE-GIT-CFLAGS
 +.PHONY: .FORCE-GIT-BUILD-OPTIONS
  
  ### Check documentation
  #
index 0000000000000000000000000000000000000000,5cd69513cf84111d1152d07f8cda77b201ffc416..b30ed734e7102b6a5d88c01364fa7d18db8f8bfe
mode 000000,100755..100755
--- /dev/null
@@@ -1,0 -1,477 +1,477 @@@
 -              if (exists $hash->{$name}{'URL'}) {
 -                      print STDERR "Warning: more than one remote.$name.url\n";
+ #!/usr/bin/perl -w
+ use strict;
+ use Git;
+ my $git = Git->repository();
+ sub add_remote_config {
+       my ($hash, $name, $what, $value) = @_;
+       if ($what eq 'url') {
 -              $hash->{$name}{'URL'} = $value;
++              # Having more than one is Ok -- it is used for push.
++              if (! exists $hash->{'URL'}) {
++                      $hash->{$name}{'URL'} = $value;
+               }
+       }
+       elsif ($what eq 'fetch') {
+               $hash->{$name}{'FETCH'} ||= [];
+               push @{$hash->{$name}{'FETCH'}}, $value;
+       }
+       elsif ($what eq 'push') {
+               $hash->{$name}{'PUSH'} ||= [];
+               push @{$hash->{$name}{'PUSH'}}, $value;
+       }
+       if (!exists $hash->{$name}{'SOURCE'}) {
+               $hash->{$name}{'SOURCE'} = 'config';
+       }
+ }
+ sub add_remote_remotes {
+       my ($hash, $file, $name) = @_;
+       if (exists $hash->{$name}) {
+               $hash->{$name}{'WARNING'} = 'ignored due to config';
+               return;
+       }
+       my $fh;
+       if (!open($fh, '<', $file)) {
+               print STDERR "Warning: cannot open $file\n";
+               return;
+       }
+       my $it = { 'SOURCE' => 'remotes' };
+       $hash->{$name} = $it;
+       while (<$fh>) {
+               chomp;
+               if (/^URL:\s*(.*)$/) {
+                       # Having more than one is Ok -- it is used for push.
+                       if (! exists $it->{'URL'}) {
+                               $it->{'URL'} = $1;
+                       }
+               }
+               elsif (/^Push:\s*(.*)$/) {
+                       $it->{'PUSH'} ||= [];
+                       push @{$it->{'PUSH'}}, $1;
+               }
+               elsif (/^Pull:\s*(.*)$/) {
+                       $it->{'FETCH'} ||= [];
+                       push @{$it->{'FETCH'}}, $1;
+               }
+               elsif (/^\#/) {
+                       ; # ignore
+               }
+               else {
+                       print STDERR "Warning: funny line in $file: $_\n";
+               }
+       }
+       close($fh);
+ }
+ sub list_remote {
+       my ($git) = @_;
+       my %seen = ();
+       my @remotes = eval {
+               $git->command(qw(config --get-regexp), '^remote\.');
+       };
+       for (@remotes) {
+               if (/^remote\.(\S+?)\.([^.\s]+)\s+(.*)$/) {
+                       add_remote_config(\%seen, $1, $2, $3);
+               }
+       }
+       my $dir = $git->repo_path() . "/remotes";
+       if (opendir(my $dh, $dir)) {
+               local $_;
+               while ($_ = readdir($dh)) {
+                       chomp;
+                       next if (! -f "$dir/$_" || ! -r _);
+                       add_remote_remotes(\%seen, "$dir/$_", $_);
+               }
+       }
+       return \%seen;
+ }
+ sub add_branch_config {
+       my ($hash, $name, $what, $value) = @_;
+       if ($what eq 'remote') {
+               if (exists $hash->{$name}{'REMOTE'}) {
+                       print STDERR "Warning: more than one branch.$name.remote\n";
+               }
+               $hash->{$name}{'REMOTE'} = $value;
+       }
+       elsif ($what eq 'merge') {
+               $hash->{$name}{'MERGE'} ||= [];
+               push @{$hash->{$name}{'MERGE'}}, $value;
+       }
+ }
+ sub list_branch {
+       my ($git) = @_;
+       my %seen = ();
+       my @branches = eval {
+               $git->command(qw(config --get-regexp), '^branch\.');
+       };
+       for (@branches) {
+               if (/^branch\.([^.]*)\.(\S*)\s+(.*)$/) {
+                       add_branch_config(\%seen, $1, $2, $3);
+               }
+       }
+       return \%seen;
+ }
+ my $remote = list_remote($git);
+ my $branch = list_branch($git);
+ sub update_ls_remote {
+       my ($harder, $info) = @_;
+       return if (($harder == 0) ||
+                  (($harder == 1) && exists $info->{'LS_REMOTE'}));
+       my @ref = map {
+               s|^[0-9a-f]{40}\s+refs/heads/||;
+               $_;
+       } $git->command(qw(ls-remote --heads), $info->{'URL'});
+       $info->{'LS_REMOTE'} = \@ref;
+ }
+ sub list_wildcard_mapping {
+       my ($forced, $ours, $ls) = @_;
+       my %refs;
+       for (@$ls) {
+               $refs{$_} = 01; # bit #0 to say "they have"
+       }
+       for ($git->command('for-each-ref', "refs/remotes/$ours")) {
+               chomp;
+               next unless (s|^[0-9a-f]{40}\s[a-z]+\srefs/remotes/$ours/||);
+               next if ($_ eq 'HEAD');
+               $refs{$_} ||= 0;
+               $refs{$_} |= 02; # bit #1 to say "we have"
+       }
+       my (@new, @stale, @tracked);
+       for (sort keys %refs) {
+               my $have = $refs{$_};
+               if ($have == 1) {
+                       push @new, $_;
+               }
+               elsif ($have == 2) {
+                       push @stale, $_;
+               }
+               elsif ($have == 3) {
+                       push @tracked, $_;
+               }
+       }
+       return \@new, \@stale, \@tracked;
+ }
+ sub list_mapping {
+       my ($name, $info) = @_;
+       my $fetch = $info->{'FETCH'};
+       my $ls = $info->{'LS_REMOTE'};
+       my (@new, @stale, @tracked);
+       for (@$fetch) {
+               next unless (/(\+)?([^:]+):(.*)/);
+               my ($forced, $theirs, $ours) = ($1, $2, $3);
+               if ($theirs eq 'refs/heads/*' &&
+                   $ours =~ /^refs\/remotes\/(.*)\/\*$/) {
+                       # wildcard mapping
+                       my ($w_new, $w_stale, $w_tracked)
+                               = list_wildcard_mapping($forced, $1, $ls);
+                       push @new, @$w_new;
+                       push @stale, @$w_stale;
+                       push @tracked, @$w_tracked;
+               }
+               elsif ($theirs =~ /\*/ || $ours =~ /\*/) {
+                       print STDERR "Warning: unrecognized mapping in remotes.$name.fetch: $_\n";
+               }
+               elsif ($theirs =~ s|^refs/heads/||) {
+                       if (!grep { $_ eq $theirs } @$ls) {
+                               push @stale, $theirs;
+                       }
+                       elsif ($ours ne '') {
+                               push @tracked, $theirs;
+                       }
+               }
+       }
+       return \@new, \@stale, \@tracked;
+ }
+ sub show_mapping {
+       my ($name, $info) = @_;
+       my ($new, $stale, $tracked) = list_mapping($name, $info);
+       if (@$new) {
+               print "  New remote branches (next fetch will store in remotes/$name)\n";
+               print "    @$new\n";
+       }
+       if (@$stale) {
+               print "  Stale tracking branches in remotes/$name (use 'git remote prune')\n";
+               print "    @$stale\n";
+       }
+       if (@$tracked) {
+               print "  Tracked remote branches\n";
+               print "    @$tracked\n";
+       }
+ }
+ sub prune_remote {
+       my ($name, $ls_remote) = @_;
+       if (!exists $remote->{$name}) {
+               print STDERR "No such remote $name\n";
+               return 1;
+       }
+       my $info = $remote->{$name};
+       update_ls_remote($ls_remote, $info);
+       my ($new, $stale, $tracked) = list_mapping($name, $info);
+       my $prefix = "refs/remotes/$name";
+       foreach my $to_prune (@$stale) {
+               my @v = $git->command(qw(rev-parse --verify), "$prefix/$to_prune");
+               $git->command(qw(update-ref -d), "$prefix/$to_prune", $v[0]);
+       }
+       return 0;
+ }
+ sub show_remote {
+       my ($name, $ls_remote) = @_;
+       if (!exists $remote->{$name}) {
+               print STDERR "No such remote $name\n";
+               return 1;
+       }
+       my $info = $remote->{$name};
+       update_ls_remote($ls_remote, $info);
+       print "* remote $name\n";
+       print "  URL: $info->{'URL'}\n";
+       for my $branchname (sort keys %$branch) {
+               next unless (defined $branch->{$branchname}{'REMOTE'} &&
+                            $branch->{$branchname}{'REMOTE'} eq $name);
+               my @merged = map {
+                       s|^refs/heads/||;
+                       $_;
+               } split(' ',"@{$branch->{$branchname}{'MERGE'}}");
+               next unless (@merged);
+               print "  Remote branch(es) merged with 'git pull' while on branch $branchname\n";
+               print "    @merged\n";
+       }
+       if ($info->{'LS_REMOTE'}) {
+               show_mapping($name, $info);
+       }
+       if ($info->{'PUSH'}) {
+               my @pushed = map {
+                       s|^refs/heads/||;
+                       s|^\+refs/heads/|+|;
+                       s|:refs/heads/|:|;
+                       $_;
+               } @{$info->{'PUSH'}};
+               print "  Local branch(es) pushed with 'git push'\n";
+               print "    @pushed\n";
+       }
+       return 0;
+ }
+ sub add_remote {
+       my ($name, $url, $opts) = @_;
+       if (exists $remote->{$name}) {
+               print STDERR "remote $name already exists.\n";
+               exit(1);
+       }
+       $git->command('config', "remote.$name.url", $url);
+       my $track = $opts->{'track'} || ["*"];
+       for (@$track) {
+               $git->command('config', '--add', "remote.$name.fetch",
+                               $opts->{'mirror'} ?
+                               "+refs/$_:refs/$_" :
+                               "+refs/heads/$_:refs/remotes/$name/$_");
+       }
+       if ($opts->{'fetch'}) {
+               $git->command('fetch', $name);
+       }
+       if (exists $opts->{'master'}) {
+               $git->command('symbolic-ref', "refs/remotes/$name/HEAD",
+                             "refs/remotes/$name/$opts->{'master'}");
+       }
+ }
+ sub update_remote {
+       my ($name) = @_;
+       my @remotes;
+         my $conf = $git->config("remotes." . $name);
+       if (defined($conf)) {
+               @remotes = split(' ', $conf);
+       } elsif ($name eq 'default') {
+               @remotes = ();
+               for (sort keys %$remote) {
+                       my $do_fetch = $git->config_bool("remote." . $_ .
+                                                   ".skipDefaultUpdate");
+                       unless ($do_fetch) {
+                               push @remotes, $_;
+                       }
+               }
+       } else {
+               print STDERR "Remote group $name does not exists.\n";
+               exit(1);
+       }
+       for (@remotes) {
+               print "Updating $_\n";
+               $git->command('fetch', "$_");
+       }
+ }
+ sub rm_remote {
+       my ($name) = @_;
+       if (!exists $remote->{$name}) {
+               print STDERR "No such remote $name\n";
+               return 1;
+       }
+       $git->command('config', '--remove-section', "remote.$name");
+       eval {
+           my @trackers = $git->command('config', '--get-regexp',
+                       'branch.*.remote', $name);
+               for (@trackers) {
+                       /^branch\.(.*)?\.remote/;
+                       $git->config('--unset', "branch.$1.remote");
+                       $git->config('--unset', "branch.$1.merge");
+               }
+       };
+       my @refs = $git->command('for-each-ref',
+               '--format=%(refname) %(objectname)', "refs/remotes/$name");
+       for (@refs) {
+               my ($ref, $object) = split;
+               $git->command(qw(update-ref -d), $ref, $object);
+       }
+       return 0;
+ }
+ sub add_usage {
+       print STDERR "Usage: git remote add [-f] [-t track]* [-m master] <name> <url>\n";
+       exit(1);
+ }
+ my $VERBOSE = 0;
+ @ARGV = grep {
+       if ($_ eq '-v' or $_ eq '--verbose') {
+               $VERBOSE=1;
+               0
+       } else {
+               1
+       }
+ } @ARGV;
+ if (!@ARGV) {
+       for (sort keys %$remote) {
+               print "$_";
+               print "\t$remote->{$_}->{URL}" if $VERBOSE;
+               print "\n";
+       }
+ }
+ elsif ($ARGV[0] eq 'show') {
+       my $ls_remote = 1;
+       my $i;
+       for ($i = 1; $i < @ARGV; $i++) {
+               if ($ARGV[$i] eq '-n') {
+                       $ls_remote = 0;
+               }
+               else {
+                       last;
+               }
+       }
+       if ($i >= @ARGV) {
+               print STDERR "Usage: git remote show <remote>\n";
+               exit(1);
+       }
+       my $status = 0;
+       for (; $i < @ARGV; $i++) {
+               $status |= show_remote($ARGV[$i], $ls_remote);
+       }
+       exit($status);
+ }
+ elsif ($ARGV[0] eq 'update') {
+       if (@ARGV <= 1) {
+               update_remote("default");
+               exit(1);
+       }
+       for (my $i = 1; $i < @ARGV; $i++) {
+               update_remote($ARGV[$i]);
+       }
+ }
+ elsif ($ARGV[0] eq 'prune') {
+       my $ls_remote = 1;
+       my $i;
+       for ($i = 1; $i < @ARGV; $i++) {
+               if ($ARGV[$i] eq '-n') {
+                       $ls_remote = 0;
+               }
+               else {
+                       last;
+               }
+       }
+       if ($i >= @ARGV) {
+               print STDERR "Usage: git remote prune <remote>\n";
+               exit(1);
+       }
+       my $status = 0;
+       for (; $i < @ARGV; $i++) {
+               $status |= prune_remote($ARGV[$i], $ls_remote);
+       }
+         exit($status);
+ }
+ elsif ($ARGV[0] eq 'add') {
+       my %opts = ();
+       while (1 < @ARGV && $ARGV[1] =~ /^-/) {
+               my $opt = $ARGV[1];
+               shift @ARGV;
+               if ($opt eq '-f' || $opt eq '--fetch') {
+                       $opts{'fetch'} = 1;
+                       next;
+               }
+               if ($opt eq '-t' || $opt eq '--track') {
+                       if (@ARGV < 1) {
+                               add_usage();
+                       }
+                       $opts{'track'} ||= [];
+                       push @{$opts{'track'}}, $ARGV[1];
+                       shift @ARGV;
+                       next;
+               }
+               if ($opt eq '-m' || $opt eq '--master') {
+                       if ((@ARGV < 1) || exists $opts{'master'}) {
+                               add_usage();
+                       }
+                       $opts{'master'} = $ARGV[1];
+                       shift @ARGV;
+                       next;
+               }
+               if ($opt eq '--mirror') {
+                       $opts{'mirror'} = 1;
+                       next;
+               }
+               add_usage();
+       }
+       if (@ARGV != 3) {
+               add_usage();
+       }
+       add_remote($ARGV[1], $ARGV[2], \%opts);
+ }
+ elsif ($ARGV[0] eq 'rm') {
+       if (@ARGV <= 1) {
+               print STDERR "Usage: git remote rm <remote>\n";
+               exit(1);
+       }
+       exit(rm_remote($ARGV[1]));
+ }
+ else {
+       print STDERR "Usage: git remote\n";
+       print STDERR "       git remote add <name> <url>\n";
+       print STDERR "       git remote rm <name>\n";
+       print STDERR "       git remote show <name>\n";
+       print STDERR "       git remote prune <name>\n";
+       print STDERR "       git remote update [group]\n";
+       exit(1);
+ }
diff --combined parse-options.c
index b32c9ea66c7ae1e83564cd25d63d5cfd6c1589b5,be35785ce88e03d23ca5b0e5ffdc6142fb931060..8e64316fe002fe90c58de61e4d6558ee4ad87157
@@@ -6,8 -6,7 +6,8 @@@
  
  struct optparse_t {
        const char **argv;
 -      int argc;
 +      const char **out;
 +      int argc, cpidx;
        const char *opt;
  };
  
@@@ -160,16 -159,6 +160,16 @@@ static int parse_long_opt(struct optpar
                        continue;
  
                rest = skip_prefix(arg, options->long_name);
 +              if (options->type == OPTION_ARGUMENT) {
 +                      if (!rest)
 +                              continue;
 +                      if (*rest == '=')
 +                              return opterror(options, "takes no value", flags);
 +                      if (*rest)
 +                              continue;
 +                      p->out[p->cpidx++] = arg - 2;
 +                      return 0;
 +              }
                if (!rest) {
                        /* abbreviated? */
                        if (!strncmp(options->long_name, arg, arg_end - arg)) {
@@@ -253,13 -242,16 +253,15 @@@ static NORETURN void usage_with_options
  int parse_options(int argc, const char **argv, const struct option *options,
                    const char * const usagestr[], int flags)
  {
 -      struct optparse_t args = { argv + 1, argc - 1, NULL };
 -      int j = 0;
 +      struct optparse_t args = { argv + 1, argv, argc - 1, 0, NULL };
  
        for (; args.argc; args.argc--, args.argv++) {
                const char *arg = args.argv[0];
  
                if (*arg != '-' || !arg[1]) {
 -                      argv[j++] = args.argv[0];
+                       if (flags & PARSE_OPT_STOP_AT_NON_OPTION)
+                               break;
 +                      args.out[args.cpidx++] = args.argv[0];
                        continue;
                }
  
                        usage_with_options(usagestr, options);
        }
  
 -      memmove(argv + j, args.argv, args.argc * sizeof(*argv));
 -      argv[j + args.argc] = NULL;
 -      return j + args.argc;
 +      memmove(args.out + args.cpidx, args.argv, args.argc * sizeof(*args.out));
 +      args.out[args.cpidx + args.argc] = NULL;
 +      return args.cpidx + args.argc;
  }
  
  #define USAGE_OPTS_WIDTH 24
@@@ -338,8 -330,6 +340,8 @@@ void usage_with_options_internal(const 
                        pos += fprintf(stderr, "--%s", opts->long_name);
  
                switch (opts->type) {
 +              case OPTION_ARGUMENT:
 +                      break;
                case OPTION_INTEGER:
                        if (opts->flags & PARSE_OPT_OPTARG)
                                pos += fprintf(stderr, " [<n>]");
diff --combined parse-options.h
index dc0807834f9135b82c289b429a8d2ea13ee53be8,0d40cd2f124edfd68d23c69433e95bd0d5796282..1af62b0485f2760607b7d3bc3e0ebdca09a53ea4
@@@ -4,7 -4,6 +4,7 @@@
  enum parse_opt_type {
        /* special types */
        OPTION_END,
 +      OPTION_ARGUMENT,
        OPTION_GROUP,
        /* options with no arguments */
        OPTION_BIT,
@@@ -19,6 -18,7 +19,7 @@@
  
  enum parse_opt_flags {
        PARSE_OPT_KEEP_DASHDASH = 1,
+       PARSE_OPT_STOP_AT_NON_OPTION = 2,
  };
  
  enum parse_opt_option_flags {
@@@ -85,7 -85,6 +86,7 @@@ struct option 
  };
  
  #define OPT_END()                   { OPTION_END }
 +#define OPT_ARGUMENT(l, h)          { OPTION_ARGUMENT, 0, (l), NULL, NULL, (h) }
  #define OPT_GROUP(h)                { OPTION_GROUP, 0, NULL, NULL, NULL, (h) }
  #define OPT_BIT(s, l, v, h, b)      { OPTION_BIT, (s), (l), (v), NULL, (h), 0, NULL, (b) }
  #define OPT_BOOLEAN(s, l, v, h)     { OPTION_BOOLEAN, (s), (l), (v), NULL, (h) }