git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH] Implement git-cp.

From
Miklos Vajna <vmiklos@frugalware.org>
Date
Feb 10, 2008, 18:24 UTC
Message-ID
<20080210182419.GP25954@genesis.frugalware.org>
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:
Show 6 quoted lines
> >  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
Previous: Miklos VajnaNext: Remi Vanicat
Message 13 of 14 in “What about git cp ?”
  1. Francis MoreauFeb 3, 2008
  2. Matthieu MoyFeb 3, 2008
  3. Jakub NarebskiFeb 3, 2008
  4. Implement git-cp.Miklos Vajna, Feb 10, 2008
  5. Johannes SchindelinFeb 10, 2008
  6. Junio C HamanoFeb 10, 2008
  7. Johannes SchindelinFeb 10, 2008
  8. Miklos VajnaFeb 10, 2008
  9. Robin RosenbergFeb 10, 2008
  10. Jakub NarebskiFeb 11, 2008
  11. Matthieu MoyFeb 11, 2008
  12. Miklos VajnaFeb 11, 2008
  13. Implement git-cp.Miklos Vajna, Feb 10, 2008
  14. Remi VanicatFeb 3, 2008

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.