4 # |
4 # |
5 # This software may be used and distributed according to the terms of the |
5 # This software may be used and distributed according to the terms of the |
6 # GNU General Public License version 2 or any later version. |
6 # GNU General Public License version 2 or any later version. |
7 |
7 |
8 from i18n import _ |
8 from i18n import _ |
9 import util, error, osutil, revset |
9 import util, error, osutil, revset, similar |
10 import os, errno, stat, sys |
10 import match as matchmod |
|
11 import os, errno, stat, sys, glob |
11 |
12 |
12 def checkfilename(f): |
13 def checkfilename(f): |
13 '''Check that the filename f is an acceptable filename for a tracked file''' |
14 '''Check that the filename f is an acceptable filename for a tracked file''' |
14 if '\r' in f or '\n' in f: |
15 if '\r' in f or '\n' in f: |
15 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f) |
16 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f) |
534 if r not in seen: |
535 if r not in seen: |
535 l.append(r) |
536 l.append(r) |
536 seen.update(l) |
537 seen.update(l) |
537 |
538 |
538 return l |
539 return l |
|
540 |
|
541 def expandpats(pats): |
|
542 if not util.expandglobs: |
|
543 return list(pats) |
|
544 ret = [] |
|
545 for p in pats: |
|
546 kind, name = matchmod._patsplit(p, None) |
|
547 if kind is None: |
|
548 try: |
|
549 globbed = glob.glob(name) |
|
550 except re.error: |
|
551 globbed = [name] |
|
552 if globbed: |
|
553 ret.extend(globbed) |
|
554 continue |
|
555 ret.append(p) |
|
556 return ret |
|
557 |
|
558 def match(repo, pats=[], opts={}, globbed=False, default='relpath'): |
|
559 if pats == ("",): |
|
560 pats = [] |
|
561 if not globbed and default == 'relpath': |
|
562 pats = expandpats(pats or []) |
|
563 m = matchmod.match(repo.root, repo.getcwd(), pats, |
|
564 opts.get('include'), opts.get('exclude'), default, |
|
565 auditor=repo.auditor) |
|
566 def badfn(f, msg): |
|
567 repo.ui.warn("%s: %s\n" % (m.rel(f), msg)) |
|
568 m.bad = badfn |
|
569 return m |
|
570 |
|
571 def matchall(repo): |
|
572 return matchmod.always(repo.root, repo.getcwd()) |
|
573 |
|
574 def matchfiles(repo, files): |
|
575 return matchmod.exact(repo.root, repo.getcwd(), files) |
|
576 |
|
577 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None): |
|
578 if dry_run is None: |
|
579 dry_run = opts.get('dry_run') |
|
580 if similarity is None: |
|
581 similarity = float(opts.get('similarity') or 0) |
|
582 # we'd use status here, except handling of symlinks and ignore is tricky |
|
583 added, unknown, deleted, removed = [], [], [], [] |
|
584 audit_path = pathauditor(repo.root) |
|
585 m = match(repo, pats, opts) |
|
586 for abs in repo.walk(m): |
|
587 target = repo.wjoin(abs) |
|
588 good = True |
|
589 try: |
|
590 audit_path(abs) |
|
591 except (OSError, util.Abort): |
|
592 good = False |
|
593 rel = m.rel(abs) |
|
594 exact = m.exact(abs) |
|
595 if good and abs not in repo.dirstate: |
|
596 unknown.append(abs) |
|
597 if repo.ui.verbose or not exact: |
|
598 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs)) |
|
599 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target) |
|
600 or (os.path.isdir(target) and not os.path.islink(target))): |
|
601 deleted.append(abs) |
|
602 if repo.ui.verbose or not exact: |
|
603 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs)) |
|
604 # for finding renames |
|
605 elif repo.dirstate[abs] == 'r': |
|
606 removed.append(abs) |
|
607 elif repo.dirstate[abs] == 'a': |
|
608 added.append(abs) |
|
609 copies = {} |
|
610 if similarity > 0: |
|
611 for old, new, score in similar.findrenames(repo, |
|
612 added + unknown, removed + deleted, similarity): |
|
613 if repo.ui.verbose or not m.exact(old) or not m.exact(new): |
|
614 repo.ui.status(_('recording removal of %s as rename to %s ' |
|
615 '(%d%% similar)\n') % |
|
616 (m.rel(old), m.rel(new), score * 100)) |
|
617 copies[new] = old |
|
618 |
|
619 if not dry_run: |
|
620 wctx = repo[None] |
|
621 wlock = repo.wlock() |
|
622 try: |
|
623 wctx.remove(deleted) |
|
624 wctx.add(unknown) |
|
625 for new, old in copies.iteritems(): |
|
626 wctx.copy(old, new) |
|
627 finally: |
|
628 wlock.release() |
|
629 |
|
630 def updatedir(ui, repo, patches, similarity=0): |
|
631 '''Update dirstate after patch application according to metadata''' |
|
632 if not patches: |
|
633 return [] |
|
634 copies = [] |
|
635 removes = set() |
|
636 cfiles = patches.keys() |
|
637 cwd = repo.getcwd() |
|
638 if cwd: |
|
639 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()] |
|
640 for f in patches: |
|
641 gp = patches[f] |
|
642 if not gp: |
|
643 continue |
|
644 if gp.op == 'RENAME': |
|
645 copies.append((gp.oldpath, gp.path)) |
|
646 removes.add(gp.oldpath) |
|
647 elif gp.op == 'COPY': |
|
648 copies.append((gp.oldpath, gp.path)) |
|
649 elif gp.op == 'DELETE': |
|
650 removes.add(gp.path) |
|
651 |
|
652 wctx = repo[None] |
|
653 for src, dst in copies: |
|
654 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd) |
|
655 if (not similarity) and removes: |
|
656 wctx.remove(sorted(removes), True) |
|
657 |
|
658 for f in patches: |
|
659 gp = patches[f] |
|
660 if gp and gp.mode: |
|
661 islink, isexec = gp.mode |
|
662 dst = repo.wjoin(gp.path) |
|
663 # patch won't create empty files |
|
664 if gp.op == 'ADD' and not os.path.lexists(dst): |
|
665 flags = (isexec and 'x' or '') + (islink and 'l' or '') |
|
666 repo.wwrite(gp.path, '', flags) |
|
667 util.setflags(dst, islink, isexec) |
|
668 addremove(repo, cfiles, similarity=similarity) |
|
669 files = patches.keys() |
|
670 files.extend([r for r in removes if r not in files]) |
|
671 return sorted(files) |
|
672 |
|
673 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None): |
|
674 """Update the dirstate to reflect the intent of copying src to dst. For |
|
675 different reasons it might not end with dst being marked as copied from src. |
|
676 """ |
|
677 origsrc = repo.dirstate.copied(src) or src |
|
678 if dst == origsrc: # copying back a copy? |
|
679 if repo.dirstate[dst] not in 'mn' and not dryrun: |
|
680 repo.dirstate.normallookup(dst) |
|
681 else: |
|
682 if repo.dirstate[origsrc] == 'a' and origsrc == src: |
|
683 if not ui.quiet: |
|
684 ui.warn(_("%s has not been committed yet, so no copy " |
|
685 "data will be stored for %s.\n") |
|
686 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd))) |
|
687 if repo.dirstate[dst] in '?r' and not dryrun: |
|
688 wctx.add([dst]) |
|
689 elif not dryrun: |
|
690 wctx.copy(origsrc, dst) |