From: Miklos Vajna <vmiklos@frugalware.org>
To: Junio C Hamano <gitster@pobox.com>
Cc: Johannes Schindelin <Johannes.Schindelin@gmx.de>,
Jakub Narebski <jnareb@gmail.com>,
Matthieu Moy <Matthieu.Moy@imag.fr>,
Francis Moreau <francis.moro@gmail.com>,
git@vger.kernel.org
Subject: [PATCH] Implement git-cp.
Date: Sun, 10 Feb 2008 19:24:19 +0100 [thread overview]
Message-ID: <20080210182419.GP25954@genesis.frugalware.org> (raw)
In-Reply-To: <alpine.LSU.1.00.0802100125510.11591@racer.site>
Actually it adds a -c option to git-mv to copy instead of move and add a
builtin alias git-cp for 'git mv -c'.
Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---
On Sun, Feb 10, 2008 at 01:26:44AM +0000, Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:
> > builtin-cp.c | 24 ++++++++++
> > builtin-mv.c | 104 ++++++++++++++++++++++++++++++++++++++++--
>
> If you touch builtin-mv.c already, why not just move cmd_cp() in there?
> It's not like it would be the first cmd_*() function living in the same
> file as other cmd_*() functions.
annotate vs blame was like this. but here is it, without builtin-cp.o.
.gitignore | 1 +
Documentation/git-cp.txt | 42 +++++++++++++++
Documentation/git-mv.txt | 12 +---
Documentation/mv-options.txt | 9 +++
builtin-mv.c | 121 ++++++++++++++++++++++++++++++++++++++++--
builtin.h | 1 +
command-list.txt | 1 +
git.c | 1 +
t/t7006-cp.sh | 75 ++++++++++++++++++++++++++
9 files changed, 249 insertions(+), 14 deletions(-)
create mode 100644 Documentation/git-cp.txt
create mode 100644 Documentation/mv-options.txt
create mode 100755 t/t7006-cp.sh
diff --git a/.gitignore b/.gitignore
index 7f8421d..3d1dcdd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,6 +26,7 @@ git-commit
git-commit-tree
git-config
git-count-objects
+git-cp
git-cvsexportcommit
git-cvsimport
git-cvsserver
diff --git a/Documentation/git-cp.txt b/Documentation/git-cp.txt
new file mode 100644
index 0000000..3109d04
--- /dev/null
+++ b/Documentation/git-cp.txt
@@ -0,0 +1,42 @@
+git-cp(1)
+=========
+
+NAME
+----
+git-cp - Copy a file, a directory, or a symlink
+
+SYNOPSIS
+--------
+'git-cp' <options>... <args>...
+
+DESCRIPTION
+-----------
+This script is used to copy a file, directory or symlink.
+
+ git-cp [-f] [-n] <source> <destination>
+ git-cp [-f] [-n] [-k] <source> ... <destination directory>
+
+In the first form, it copies <source>, which must exist and be either
+a file, symlink or directory, to <destination>.
+In the second form, the last argument has to be an existing
+directory; the given sources will be copied into this directory.
+
+The index is updated after successful completion, but the change must still be
+committed.
+
+
+OPTIONS
+-------
+include::mv-options.txt[]
+
+SEE ALSO
+--------
+linkgit:git-mv[1]
+
+AUTHOR
+------
+Written by Miklos Vajna <vmiklos@frugalware.org>.
+
+GIT
+---
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-mv.txt b/Documentation/git-mv.txt
index bff3fbe..45d8255 100644
--- a/Documentation/git-mv.txt
+++ b/Documentation/git-mv.txt
@@ -27,15 +27,9 @@ committed.
OPTIONS
-------
--f::
- Force renaming or moving of a file even if the target exists
--k::
- Skip move or rename actions which would lead to an error
- condition. An error happens when a source is neither existing nor
- controlled by GIT, or when it would overwrite an existing
- file unless '-f' is given.
--n, \--dry-run::
- Do nothing; only show what would happen
+include::mv-options.txt[]
+-c::
+ Copy instead of move.
Author
diff --git a/Documentation/mv-options.txt b/Documentation/mv-options.txt
new file mode 100644
index 0000000..ad78d1e
--- /dev/null
+++ b/Documentation/mv-options.txt
@@ -0,0 +1,9 @@
+-f::
+ Force renaming or moving of a file even if the target exists
+-k::
+ Skip move or rename actions which would lead to an error
+ condition. An error happens when a source is neither existing nor
+ controlled by GIT, or when it would overwrite an existing
+ file unless '-f' is given.
+-n, \--dry-run::
+ Do nothing; only show what would happen
diff --git a/builtin-mv.c b/builtin-mv.c
index 990e213..08a5526 100644
--- a/builtin-mv.c
+++ b/builtin-mv.c
@@ -60,14 +60,103 @@ static const char *add_slash(const char *path)
return path;
}
+static int copyfile(const char *src_name, const char *dst_name)
+{
+ int ifd;
+ int ofd;
+ struct stat sb;
+ int cnt;
+ char buf[1024];
+
+ stat(src_name, &sb);
+ if ((ifd = open (src_name, O_RDONLY)) < 0)
+ die("could not open file %s for reading: %s", src_name, strerror(errno));
+ if ((ofd =
+ open (dst_name, O_WRONLY | O_CREAT | O_TRUNC, 0)) < 0
+ || chmod (dst_name, sb.st_mode & 07777))
+ die("could not open file %s for writing: %s", dst_name, strerror(errno));
+ while ((cnt = read (ifd, buf, sizeof buf)) > 0) {
+ if (write (ofd, buf, cnt) != cnt)
+ die("could not write to file %s", dst_name);
+ }
+ close (ifd);
+ close (ofd);
+ return 0;
+}
+
+static int copytree (const char *src_root, const char *dst_root)
+{
+ char src_name[1024];
+ char dst_name[1024];
+ int err = 0;
+ struct dirent *ent;
+ struct stat sb;
+ DIR *dir;
+
+ lstat(src_root, &sb);
+
+ if (!(dir = opendir (src_root)))
+ return copyfile(src_root, dst_root);
+ else
+ mkdir(dst_root, sb.st_mode);
+
+ while ((ent = readdir (dir))) {
+
+ if (strcmp (ent->d_name, ".") == 0 ||
+ strcmp (ent->d_name, "..") == 0)
+ continue;
+
+ snprintf (src_name, sizeof src_name, "%s/%s", src_root,
+ ent->d_name);
+
+ snprintf (dst_name, sizeof dst_name, "%s/%s", dst_root,
+ ent->d_name);
+
+ if (lstat (src_name, &sb) < 0)
+ continue;
+
+ if (S_ISDIR (sb.st_mode)) {
+
+ if (mkdir (dst_name, sb.st_mode)
+ || chmod (dst_name, sb.st_mode & 07777)
+ || copytree (src_name, dst_name)) {
+ break;
+ }
+ continue;
+ }
+
+ if (S_ISLNK (sb.st_mode)) {
+ char oldlink[1024];
+ char dummy[1024];
+ int len;
+
+ if ((len =
+ readlink (src_name, oldlink,
+ sizeof (oldlink) - 1)) < 0)
+ die("could not read symlink %s: %s", src_name, strerror(errno));
+ oldlink[len] = '\0'; /* readlink() does not NULL-terminate */
+ if (symlink (oldlink, dst_name))
+ die("could not create symlink %s: %s", dst_name, strerror(errno));
+ continue;
+ }
+
+ if (copyfile(src_name, dst_name) == -1)
+ break;
+ }
+ closedir (dir);
+
+ return 0;
+}
+
static struct lock_file lock_file;
int cmd_mv(int argc, const char **argv, const char *prefix)
{
int i, newfd;
- int verbose = 0, show_only = 0, force = 0, ignore_errors = 0;
+ int verbose = 0, show_only = 0, copy = 0, force = 0, ignore_errors = 0;
struct option builtin_mv_options[] = {
OPT__DRY_RUN(&show_only),
+ OPT_BOOLEAN('c', NULL, ©, "copy instead of move/rename"),
OPT_BOOLEAN('f', NULL, &force, "force move/rename even if target exists"),
OPT_BOOLEAN('k', NULL, &ignore_errors, "skip move/rename errors"),
OPT_END(),
@@ -220,15 +309,20 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
enum update_mode mode = modes[i];
if (show_only || verbose)
printf("Renaming %s to %s\n", src, dst);
- if (!show_only && mode != INDEX &&
- rename(src, dst) < 0 && !ignore_errors)
- die ("renaming %s failed: %s", src, strerror(errno));
+ if (!show_only && mode != INDEX)
+ {
+ if(!copy && rename(src, dst) < 0 && !ignore_errors)
+ die ("renaming %s failed: %s", src, strerror(errno));
+ else if (copytree(src, dst) < 0 && !ignore_errors)
+ die ("copying %s failed: %s", src, strerror(errno));
+ }
if (mode == WORKING_DIRECTORY)
continue;
if (cache_name_pos(src, strlen(src)) >= 0) {
- path_list_insert(src, &deleted);
+ if(!copy)
+ path_list_insert(src, &deleted);
/* destination can be a directory with 1 file inside */
if (path_list_has_path(&overwritten, dst))
@@ -271,3 +365,20 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
return 0;
}
+
+int cmd_cp(int argc, const char **argv, const char *prefix)
+{
+ const char **nargv;
+ int i;
+ nargv = xmalloc(sizeof(char *) * (argc + 2));
+
+ nargv[0] = "mv";
+ nargv[1] = "-c";
+
+ for (i = 1; i < argc; i++) {
+ nargv[i+1] = argv[i];
+ }
+ nargv[argc + 1] = NULL;
+
+ return cmd_mv(argc + 1, nargv, prefix);
+}
diff --git a/builtin.h b/builtin.h
index cb675c4..e1a85fe 100644
--- a/builtin.h
+++ b/builtin.h
@@ -28,6 +28,7 @@ extern int cmd_clean(int argc, const char **argv, const char *prefix);
extern int cmd_commit(int argc, const char **argv, const char *prefix);
extern int cmd_commit_tree(int argc, const char **argv, const char *prefix);
extern int cmd_count_objects(int argc, const char **argv, const char *prefix);
+extern int cmd_cp(int argc, const char **argv, const char *prefix);
extern int cmd_describe(int argc, const char **argv, const char *prefix);
extern int cmd_diff_files(int argc, const char **argv, const char *prefix);
extern int cmd_diff_index(int argc, const char **argv, const char *prefix);
diff --git a/command-list.txt b/command-list.txt
index 3583a33..ddfcdaf 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -24,6 +24,7 @@ git-commit mainporcelain common
git-commit-tree plumbingmanipulators
git-config ancillarymanipulators
git-count-objects ancillaryinterrogators
+git-cp mainporcelain common
git-cvsexportcommit foreignscminterface
git-cvsimport foreignscminterface
git-cvsserver foreignscminterface
diff --git a/git.c b/git.c
index 15fec89..f6a104b 100644
--- a/git.c
+++ b/git.c
@@ -298,6 +298,7 @@ static void handle_internal_command(int argc, const char **argv)
{ "commit-tree", cmd_commit_tree, RUN_SETUP },
{ "config", cmd_config },
{ "count-objects", cmd_count_objects, RUN_SETUP },
+ { "cp", cmd_cp, RUN_SETUP | NEED_WORK_TREE },
{ "describe", cmd_describe, RUN_SETUP },
{ "diff", cmd_diff },
{ "diff-files", cmd_diff_files },
diff --git a/t/t7006-cp.sh b/t/t7006-cp.sh
new file mode 100755
index 0000000..217072c
--- /dev/null
+++ b/t/t7006-cp.sh
@@ -0,0 +1,75 @@
+#!/bin/sh
+
+test_description='git cp in subdirs'
+. ./test-lib.sh
+
+test_expect_success \
+ 'prepare reference tree' \
+ 'mkdir path0 path1 &&
+ cp ../../COPYING path0/COPYING &&
+ git add path0/COPYING &&
+ git-commit -m add -a'
+
+test_expect_success \
+ 'copying the file out of subdirectory' \
+ 'cd path0 && git cp COPYING ../path1/COPYING'
+
+# in path0 currently
+test_expect_success \
+ 'commiting the change' \
+ 'cd .. && git-commit -m copy-out -a'
+
+test_expect_success \
+ 'checking the commit' \
+ 'git diff-tree -r -C --find-copies-harder --name-status HEAD^ HEAD | \
+ grep "^C100..*path0/COPYING..*path1/COPYING"'
+
+test_expect_success \
+ 'adding another file' \
+ 'cp ../../README path0/README &&
+ git add path0/README &&
+ git-commit -m add2 -a'
+
+test_expect_success \
+ 'copying whole subdirectory' \
+ 'git cp path0 path2'
+
+test_expect_success \
+ 'commiting the change' \
+ 'git-commit -m dir-copy -a'
+
+test_expect_success \
+ 'checking the commit' \
+ 'git diff-tree -r -C --find-copies-harder --name-status HEAD^ HEAD | \
+ grep "^C100..*path0/COPYING..*path2/COPYING" &&
+ git diff-tree -r -C --find-copies-harder --name-status HEAD^ HEAD | \
+ grep "^C100..*path0/README..*path2/README"'
+
+test_expect_success \
+ 'succeed when source is a prefix of destination' \
+ 'git cp path2/COPYING path2/COPYING-copied'
+
+test_expect_success \
+ 'copying whole subdirectory into subdirectory' \
+ 'git cp path2 path1'
+
+test_expect_success \
+ 'commiting the change' \
+ 'git-commit -m dir-move -a'
+
+test_expect_success \
+ 'checking the commit' \
+ 'git diff-tree -r -C --find-copies-harder --name-status HEAD^ HEAD | \
+ grep "^C100..*path0/COPYING..*path1/path2/COPYING" &&
+ git diff-tree -r -C --find-copies-harder --name-status HEAD^ HEAD | \
+ grep "^C100..*path0/README..*path1/path2/README"'
+
+test_expect_success \
+ 'do not copy directory over existing directory' \
+ 'rm -rf path0 && mkdir path0 && mkdir path0/path2 && ! git cp path2 path0'
+
+test_expect_success \
+ 'copy "."' \
+ 'rm -rf path2 && git cp path1/path2/ .'
+
+test_done
--
1.5.4
next prev parent reply other threads:[~2008-02-10 18:25 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2008-02-03 18:23 What about git cp ? Francis Moreau
2008-02-03 18:46 ` Matthieu Moy
2008-02-03 19:07 ` Jakub Narebski
2008-02-10 1:12 ` [RFC/PATCH] Implement git-cp Miklos Vajna
2008-02-10 1:26 ` Johannes Schindelin
2008-02-10 7:49 ` Junio C Hamano
2008-02-10 12:33 ` Johannes Schindelin
2008-02-10 12:42 ` Miklos Vajna
2008-02-10 13:29 ` Robin Rosenberg
2008-02-11 1:30 ` Jakub Narebski
2008-02-11 10:18 ` Matthieu Moy
2008-02-11 13:43 ` Miklos Vajna
2008-02-10 18:24 ` Miklos Vajna [this message]
2008-02-03 18:55 ` What about git cp ? Remi Vanicat
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20080210182419.GP25954@genesis.frugalware.org \
--to=vmiklos@frugalware.org \
--cc=Johannes.Schindelin@gmx.de \
--cc=Matthieu.Moy@imag.fr \
--cc=francis.moro@gmail.com \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
--cc=jnareb@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).