All of lore.kernel.org
 help / color / mirror / Atom feed
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, "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

  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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.