Fossil SCM
Allow the REPOSITORY argument to "fossil open" to be a URI, in which case the URI is cloned first and then the clone is opened.
Commit
dfc5ceed730b6a659f20ca009bf405f53ac1f0498089a52d00922bcef0946840
Parent
0629d2a0d70de6f…
1 file changed
+66
-11
M
src/db.c
+66
-11
| --- src/db.c | ||
| +++ src/db.c | ||
| @@ -3070,29 +3070,41 @@ | ||
| 3070 | 3070 | } |
| 3071 | 3071 | |
| 3072 | 3072 | /* |
| 3073 | 3073 | ** COMMAND: open |
| 3074 | 3074 | ** |
| 3075 | -** Usage: %fossil open FILENAME ?VERSION? ?OPTIONS? | |
| 3075 | +** Usage: %fossil open REPOSITORY ?VERSION? ?OPTIONS? | |
| 3076 | 3076 | ** |
| 3077 | -** Open a connection to the local repository in FILENAME. A checkout | |
| 3078 | -** for the repository is created with its root at the working directory. | |
| 3077 | +** Open a new connection to the repository name REPOSITORY. A checkout | |
| 3078 | +** for the repository is created with its root at the current working | |
| 3079 | +** directory, or at some other directory identified by "--workdir DIR". | |
| 3079 | 3080 | ** If VERSION is specified then that version is checked out. Otherwise |
| 3080 | -** the latest version is checked out. No files other than "manifest" | |
| 3081 | -** and "manifest.uuid" are modified if the --keep option is present. | |
| 3081 | +** the most recent check-in on the main branch (usually "trunk") is used. | |
| 3082 | +** | |
| 3083 | +** REPOSITORY is usually a filename for a repository that already exists | |
| 3084 | +** on the local machine. But REPOSITORY can also be a URI, in which case | |
| 3085 | +** the URI is first cloned and the clone is opened. The clone will be stored | |
| 3086 | +** in the current directory, or in an alternative directory specified by | |
| 3087 | +** the --repodir option. | |
| 3088 | +** | |
| 3089 | +** No files other than "manifest" and "manifest.uuid" are modified if | |
| 3090 | +** the --keep option is present. | |
| 3082 | 3091 | ** |
| 3083 | 3092 | ** Options: |
| 3084 | 3093 | ** --empty Initialize checkout as being empty, but still connected |
| 3085 | 3094 | ** with the local repository. If you commit this checkout, |
| 3086 | 3095 | ** it will become a new "initial" commit in the repository. |
| 3096 | +** --force-missing Force opening a repository with missing content | |
| 3087 | 3097 | ** --keep Only modify the manifest and manifest.uuid files |
| 3088 | 3098 | ** --nested Allow opening a repository inside an opened checkout |
| 3089 | -** --force-missing Force opening a repository with missing content | |
| 3099 | +** --repodir DIR If REPOSITORY is a URI that will be cloned, store | |
| 3100 | +** the clone in DIR rather than in "." | |
| 3090 | 3101 | ** --setmtime Set timestamps of all files to match their SCM-side |
| 3091 | 3102 | ** times (the timestamp of the last checkin which modified |
| 3092 | 3103 | ** them). |
| 3093 | -** --workdir DIR Use DIR as the working directory instead of ".". | |
| 3104 | +** --workdir DIR Use DIR as the working directory instead of ".". The DIR | |
| 3105 | +** directory is created if it does not previously exist. | |
| 3094 | 3106 | ** |
| 3095 | 3107 | ** See also: close |
| 3096 | 3108 | */ |
| 3097 | 3109 | void cmd_open(void){ |
| 3098 | 3110 | int emptyFlag; |
| @@ -3102,19 +3114,21 @@ | ||
| 3102 | 3114 | int allowSymlinks; |
| 3103 | 3115 | int setmtimeFlag; /* --setmtime. Set mtimes on files */ |
| 3104 | 3116 | static char *azNewArgv[] = { 0, "checkout", "--prompt", 0, 0, 0, 0 }; |
| 3105 | 3117 | const char *zWorkDir; /* --workdir value */ |
| 3106 | 3118 | const char *zRepo = 0; /* Name of the repository file */ |
| 3119 | + const char *zRepoDir = 0; /* --repodir value */ | |
| 3107 | 3120 | Blob normalizedRepoName; /* Normalized repository filename */ |
| 3108 | 3121 | |
| 3109 | 3122 | url_proxy_options(); |
| 3110 | 3123 | emptyFlag = find_option("empty",0,0)!=0; |
| 3111 | 3124 | keepFlag = find_option("keep",0,0)!=0; |
| 3112 | 3125 | forceMissingFlag = find_option("force-missing",0,0)!=0; |
| 3113 | 3126 | allowNested = find_option("nested",0,0)!=0; |
| 3114 | 3127 | setmtimeFlag = find_option("setmtime",0,0)!=0; |
| 3115 | 3128 | zWorkDir = find_option("workdir",0,1); |
| 3129 | + zRepoDir = find_option("repodir",0,1); | |
| 3116 | 3130 | |
| 3117 | 3131 | |
| 3118 | 3132 | /* We should be done with options.. */ |
| 3119 | 3133 | verify_all_options(); |
| 3120 | 3134 | |
| @@ -3121,23 +3135,64 @@ | ||
| 3121 | 3135 | if( g.argc!=3 && g.argc!=4 ){ |
| 3122 | 3136 | usage("REPOSITORY-FILENAME ?VERSION?"); |
| 3123 | 3137 | } |
| 3124 | 3138 | zRepo = g.argv[2]; |
| 3125 | 3139 | blob_init(&normalizedRepoName, 0, 0); |
| 3140 | + | |
| 3141 | + /* If REPOSITORY looks like a URI, then try to clone it first */ | |
| 3142 | + if( sqlite3_strglob("http://*", zRepo)==0 | |
| 3143 | + || sqlite3_strglob("https://*", zRepo)==0 | |
| 3144 | + || sqlite3_strglob("ssh:*", zRepo)==0 | |
| 3145 | + || sqlite3_strglob("file:*", zRepo)==0 | |
| 3146 | + ){ | |
| 3147 | + char *zNewBase; /* Base name of the cloned repository file */ | |
| 3148 | + const char *zUri; /* URI to clone */ | |
| 3149 | + int i; /* Loop counter */ | |
| 3150 | + int rc; /* Result code from fossil_system() */ | |
| 3151 | + Blob cmd; /* Clone command to be run */ | |
| 3152 | + char *zCmd; /* String version of the clone command */ | |
| 3153 | + | |
| 3154 | + zUri = zRepo; | |
| 3155 | + zNewBase = fossil_strdup(file_tail(zUri)); | |
| 3156 | + for(i=(int)strlen(zNewBase)-1; i>1 && zNewBase[i]!='.'; i--){} | |
| 3157 | + if( zNewBase[i]=='.' ) zNewBase[i] = 0; | |
| 3158 | + if( zRepoDir==0 ) zRepoDir = "."; | |
| 3159 | + zRepo = mprintf("%s/%s.fossil", zRepoDir, zNewBase); | |
| 3160 | + fossil_free(zNewBase); | |
| 3161 | + blob_init(&cmd, 0, 0); | |
| 3162 | + blob_append_escaped_arg(&cmd, g.nameOfExe); | |
| 3163 | + blob_append(&cmd, " clone", -1); | |
| 3164 | + blob_append_escaped_arg(&cmd, zUri); | |
| 3165 | + blob_append_escaped_arg(&cmd, zRepo); | |
| 3166 | + zCmd = blob_str(&cmd); | |
| 3167 | + fossil_print("%s\n", zCmd); | |
| 3168 | + rc = fossil_system(zCmd); | |
| 3169 | + if( rc ){ | |
| 3170 | + fossil_fatal("clone of %s failed", zUri); | |
| 3171 | + } | |
| 3172 | + blob_reset(&cmd); | |
| 3173 | + }else if( zRepoDir ){ | |
| 3174 | + fossil_fatal("the --repodir option only makes sense if the REPOSITORY " | |
| 3175 | + "argument is a URI that begins with http:, https:, ssh:, " | |
| 3176 | + "or file:"); | |
| 3177 | + } | |
| 3178 | + | |
| 3179 | + /* If --workdir is specified, change to the requested working directory */ | |
| 3126 | 3180 | if( zWorkDir ){ |
| 3127 | 3181 | file_canonical_name(zRepo, &normalizedRepoName, 0); |
| 3128 | 3182 | zRepo = blob_str(&normalizedRepoName); |
| 3129 | 3183 | if( file_isdir(zWorkDir, ExtFILE)!=1 ){ |
| 3130 | 3184 | file_mkfolder(zWorkDir, ExtFILE, 0, 0); |
| 3131 | 3185 | if( file_mkdir(zWorkDir, ExtFILE, 0) ){ |
| 3132 | - fossil_fatal("cannot create directory %s\n", zWorkDir); | |
| 3186 | + fossil_fatal("cannot create directory %s", zWorkDir); | |
| 3133 | 3187 | } |
| 3134 | 3188 | } |
| 3135 | 3189 | if( file_chdir(zWorkDir, 0) ){ |
| 3136 | - fossil_fatal("unable to make %s the working directory\n", zWorkDir); | |
| 3190 | + fossil_fatal("unable to make %s the working directory", zWorkDir); | |
| 3137 | 3191 | } |
| 3138 | 3192 | } |
| 3193 | + | |
| 3139 | 3194 | if( !allowNested && db_open_local(0) ){ |
| 3140 | 3195 | fossil_fatal("already within an open tree rooted at %s", g.zLocalRoot); |
| 3141 | 3196 | } |
| 3142 | 3197 | db_open_repository(zRepo); |
| 3143 | 3198 | |
| @@ -3190,12 +3245,12 @@ | ||
| 3190 | 3245 | ** point, this will probably be the setting value from the |
| 3191 | 3246 | ** repository or global configuration databases. */ |
| 3192 | 3247 | g.allowSymlinks = db_get_boolean("allow-symlinks", |
| 3193 | 3248 | db_allow_symlinks_by_default()); |
| 3194 | 3249 | } |
| 3195 | - db_lset("repository", g.argv[2]); | |
| 3196 | - db_record_repository_filename(g.argv[2]); | |
| 3250 | + db_lset("repository", zRepo); | |
| 3251 | + db_record_repository_filename(zRepo); | |
| 3197 | 3252 | db_set_checkout(0); |
| 3198 | 3253 | azNewArgv[0] = g.argv[0]; |
| 3199 | 3254 | g.argv = azNewArgv; |
| 3200 | 3255 | if( !emptyFlag ){ |
| 3201 | 3256 | g.argc = 3; |
| 3202 | 3257 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -3070,29 +3070,41 @@ | |
| 3070 | } |
| 3071 | |
| 3072 | /* |
| 3073 | ** COMMAND: open |
| 3074 | ** |
| 3075 | ** Usage: %fossil open FILENAME ?VERSION? ?OPTIONS? |
| 3076 | ** |
| 3077 | ** Open a connection to the local repository in FILENAME. A checkout |
| 3078 | ** for the repository is created with its root at the working directory. |
| 3079 | ** If VERSION is specified then that version is checked out. Otherwise |
| 3080 | ** the latest version is checked out. No files other than "manifest" |
| 3081 | ** and "manifest.uuid" are modified if the --keep option is present. |
| 3082 | ** |
| 3083 | ** Options: |
| 3084 | ** --empty Initialize checkout as being empty, but still connected |
| 3085 | ** with the local repository. If you commit this checkout, |
| 3086 | ** it will become a new "initial" commit in the repository. |
| 3087 | ** --keep Only modify the manifest and manifest.uuid files |
| 3088 | ** --nested Allow opening a repository inside an opened checkout |
| 3089 | ** --force-missing Force opening a repository with missing content |
| 3090 | ** --setmtime Set timestamps of all files to match their SCM-side |
| 3091 | ** times (the timestamp of the last checkin which modified |
| 3092 | ** them). |
| 3093 | ** --workdir DIR Use DIR as the working directory instead of ".". |
| 3094 | ** |
| 3095 | ** See also: close |
| 3096 | */ |
| 3097 | void cmd_open(void){ |
| 3098 | int emptyFlag; |
| @@ -3102,19 +3114,21 @@ | |
| 3102 | int allowSymlinks; |
| 3103 | int setmtimeFlag; /* --setmtime. Set mtimes on files */ |
| 3104 | static char *azNewArgv[] = { 0, "checkout", "--prompt", 0, 0, 0, 0 }; |
| 3105 | const char *zWorkDir; /* --workdir value */ |
| 3106 | const char *zRepo = 0; /* Name of the repository file */ |
| 3107 | Blob normalizedRepoName; /* Normalized repository filename */ |
| 3108 | |
| 3109 | url_proxy_options(); |
| 3110 | emptyFlag = find_option("empty",0,0)!=0; |
| 3111 | keepFlag = find_option("keep",0,0)!=0; |
| 3112 | forceMissingFlag = find_option("force-missing",0,0)!=0; |
| 3113 | allowNested = find_option("nested",0,0)!=0; |
| 3114 | setmtimeFlag = find_option("setmtime",0,0)!=0; |
| 3115 | zWorkDir = find_option("workdir",0,1); |
| 3116 | |
| 3117 | |
| 3118 | /* We should be done with options.. */ |
| 3119 | verify_all_options(); |
| 3120 | |
| @@ -3121,23 +3135,64 @@ | |
| 3121 | if( g.argc!=3 && g.argc!=4 ){ |
| 3122 | usage("REPOSITORY-FILENAME ?VERSION?"); |
| 3123 | } |
| 3124 | zRepo = g.argv[2]; |
| 3125 | blob_init(&normalizedRepoName, 0, 0); |
| 3126 | if( zWorkDir ){ |
| 3127 | file_canonical_name(zRepo, &normalizedRepoName, 0); |
| 3128 | zRepo = blob_str(&normalizedRepoName); |
| 3129 | if( file_isdir(zWorkDir, ExtFILE)!=1 ){ |
| 3130 | file_mkfolder(zWorkDir, ExtFILE, 0, 0); |
| 3131 | if( file_mkdir(zWorkDir, ExtFILE, 0) ){ |
| 3132 | fossil_fatal("cannot create directory %s\n", zWorkDir); |
| 3133 | } |
| 3134 | } |
| 3135 | if( file_chdir(zWorkDir, 0) ){ |
| 3136 | fossil_fatal("unable to make %s the working directory\n", zWorkDir); |
| 3137 | } |
| 3138 | } |
| 3139 | if( !allowNested && db_open_local(0) ){ |
| 3140 | fossil_fatal("already within an open tree rooted at %s", g.zLocalRoot); |
| 3141 | } |
| 3142 | db_open_repository(zRepo); |
| 3143 | |
| @@ -3190,12 +3245,12 @@ | |
| 3190 | ** point, this will probably be the setting value from the |
| 3191 | ** repository or global configuration databases. */ |
| 3192 | g.allowSymlinks = db_get_boolean("allow-symlinks", |
| 3193 | db_allow_symlinks_by_default()); |
| 3194 | } |
| 3195 | db_lset("repository", g.argv[2]); |
| 3196 | db_record_repository_filename(g.argv[2]); |
| 3197 | db_set_checkout(0); |
| 3198 | azNewArgv[0] = g.argv[0]; |
| 3199 | g.argv = azNewArgv; |
| 3200 | if( !emptyFlag ){ |
| 3201 | g.argc = 3; |
| 3202 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -3070,29 +3070,41 @@ | |
| 3070 | } |
| 3071 | |
| 3072 | /* |
| 3073 | ** COMMAND: open |
| 3074 | ** |
| 3075 | ** Usage: %fossil open REPOSITORY ?VERSION? ?OPTIONS? |
| 3076 | ** |
| 3077 | ** Open a new connection to the repository name REPOSITORY. A checkout |
| 3078 | ** for the repository is created with its root at the current working |
| 3079 | ** directory, or at some other directory identified by "--workdir DIR". |
| 3080 | ** If VERSION is specified then that version is checked out. Otherwise |
| 3081 | ** the most recent check-in on the main branch (usually "trunk") is used. |
| 3082 | ** |
| 3083 | ** REPOSITORY is usually a filename for a repository that already exists |
| 3084 | ** on the local machine. But REPOSITORY can also be a URI, in which case |
| 3085 | ** the URI is first cloned and the clone is opened. The clone will be stored |
| 3086 | ** in the current directory, or in an alternative directory specified by |
| 3087 | ** the --repodir option. |
| 3088 | ** |
| 3089 | ** No files other than "manifest" and "manifest.uuid" are modified if |
| 3090 | ** the --keep option is present. |
| 3091 | ** |
| 3092 | ** Options: |
| 3093 | ** --empty Initialize checkout as being empty, but still connected |
| 3094 | ** with the local repository. If you commit this checkout, |
| 3095 | ** it will become a new "initial" commit in the repository. |
| 3096 | ** --force-missing Force opening a repository with missing content |
| 3097 | ** --keep Only modify the manifest and manifest.uuid files |
| 3098 | ** --nested Allow opening a repository inside an opened checkout |
| 3099 | ** --repodir DIR If REPOSITORY is a URI that will be cloned, store |
| 3100 | ** the clone in DIR rather than in "." |
| 3101 | ** --setmtime Set timestamps of all files to match their SCM-side |
| 3102 | ** times (the timestamp of the last checkin which modified |
| 3103 | ** them). |
| 3104 | ** --workdir DIR Use DIR as the working directory instead of ".". The DIR |
| 3105 | ** directory is created if it does not previously exist. |
| 3106 | ** |
| 3107 | ** See also: close |
| 3108 | */ |
| 3109 | void cmd_open(void){ |
| 3110 | int emptyFlag; |
| @@ -3102,19 +3114,21 @@ | |
| 3114 | int allowSymlinks; |
| 3115 | int setmtimeFlag; /* --setmtime. Set mtimes on files */ |
| 3116 | static char *azNewArgv[] = { 0, "checkout", "--prompt", 0, 0, 0, 0 }; |
| 3117 | const char *zWorkDir; /* --workdir value */ |
| 3118 | const char *zRepo = 0; /* Name of the repository file */ |
| 3119 | const char *zRepoDir = 0; /* --repodir value */ |
| 3120 | Blob normalizedRepoName; /* Normalized repository filename */ |
| 3121 | |
| 3122 | url_proxy_options(); |
| 3123 | emptyFlag = find_option("empty",0,0)!=0; |
| 3124 | keepFlag = find_option("keep",0,0)!=0; |
| 3125 | forceMissingFlag = find_option("force-missing",0,0)!=0; |
| 3126 | allowNested = find_option("nested",0,0)!=0; |
| 3127 | setmtimeFlag = find_option("setmtime",0,0)!=0; |
| 3128 | zWorkDir = find_option("workdir",0,1); |
| 3129 | zRepoDir = find_option("repodir",0,1); |
| 3130 | |
| 3131 | |
| 3132 | /* We should be done with options.. */ |
| 3133 | verify_all_options(); |
| 3134 | |
| @@ -3121,23 +3135,64 @@ | |
| 3135 | if( g.argc!=3 && g.argc!=4 ){ |
| 3136 | usage("REPOSITORY-FILENAME ?VERSION?"); |
| 3137 | } |
| 3138 | zRepo = g.argv[2]; |
| 3139 | blob_init(&normalizedRepoName, 0, 0); |
| 3140 | |
| 3141 | /* If REPOSITORY looks like a URI, then try to clone it first */ |
| 3142 | if( sqlite3_strglob("http://*", zRepo)==0 |
| 3143 | || sqlite3_strglob("https://*", zRepo)==0 |
| 3144 | || sqlite3_strglob("ssh:*", zRepo)==0 |
| 3145 | || sqlite3_strglob("file:*", zRepo)==0 |
| 3146 | ){ |
| 3147 | char *zNewBase; /* Base name of the cloned repository file */ |
| 3148 | const char *zUri; /* URI to clone */ |
| 3149 | int i; /* Loop counter */ |
| 3150 | int rc; /* Result code from fossil_system() */ |
| 3151 | Blob cmd; /* Clone command to be run */ |
| 3152 | char *zCmd; /* String version of the clone command */ |
| 3153 | |
| 3154 | zUri = zRepo; |
| 3155 | zNewBase = fossil_strdup(file_tail(zUri)); |
| 3156 | for(i=(int)strlen(zNewBase)-1; i>1 && zNewBase[i]!='.'; i--){} |
| 3157 | if( zNewBase[i]=='.' ) zNewBase[i] = 0; |
| 3158 | if( zRepoDir==0 ) zRepoDir = "."; |
| 3159 | zRepo = mprintf("%s/%s.fossil", zRepoDir, zNewBase); |
| 3160 | fossil_free(zNewBase); |
| 3161 | blob_init(&cmd, 0, 0); |
| 3162 | blob_append_escaped_arg(&cmd, g.nameOfExe); |
| 3163 | blob_append(&cmd, " clone", -1); |
| 3164 | blob_append_escaped_arg(&cmd, zUri); |
| 3165 | blob_append_escaped_arg(&cmd, zRepo); |
| 3166 | zCmd = blob_str(&cmd); |
| 3167 | fossil_print("%s\n", zCmd); |
| 3168 | rc = fossil_system(zCmd); |
| 3169 | if( rc ){ |
| 3170 | fossil_fatal("clone of %s failed", zUri); |
| 3171 | } |
| 3172 | blob_reset(&cmd); |
| 3173 | }else if( zRepoDir ){ |
| 3174 | fossil_fatal("the --repodir option only makes sense if the REPOSITORY " |
| 3175 | "argument is a URI that begins with http:, https:, ssh:, " |
| 3176 | "or file:"); |
| 3177 | } |
| 3178 | |
| 3179 | /* If --workdir is specified, change to the requested working directory */ |
| 3180 | if( zWorkDir ){ |
| 3181 | file_canonical_name(zRepo, &normalizedRepoName, 0); |
| 3182 | zRepo = blob_str(&normalizedRepoName); |
| 3183 | if( file_isdir(zWorkDir, ExtFILE)!=1 ){ |
| 3184 | file_mkfolder(zWorkDir, ExtFILE, 0, 0); |
| 3185 | if( file_mkdir(zWorkDir, ExtFILE, 0) ){ |
| 3186 | fossil_fatal("cannot create directory %s", zWorkDir); |
| 3187 | } |
| 3188 | } |
| 3189 | if( file_chdir(zWorkDir, 0) ){ |
| 3190 | fossil_fatal("unable to make %s the working directory", zWorkDir); |
| 3191 | } |
| 3192 | } |
| 3193 | |
| 3194 | if( !allowNested && db_open_local(0) ){ |
| 3195 | fossil_fatal("already within an open tree rooted at %s", g.zLocalRoot); |
| 3196 | } |
| 3197 | db_open_repository(zRepo); |
| 3198 | |
| @@ -3190,12 +3245,12 @@ | |
| 3245 | ** point, this will probably be the setting value from the |
| 3246 | ** repository or global configuration databases. */ |
| 3247 | g.allowSymlinks = db_get_boolean("allow-symlinks", |
| 3248 | db_allow_symlinks_by_default()); |
| 3249 | } |
| 3250 | db_lset("repository", zRepo); |
| 3251 | db_record_repository_filename(zRepo); |
| 3252 | db_set_checkout(0); |
| 3253 | azNewArgv[0] = g.argv[0]; |
| 3254 | g.argv = azNewArgv; |
| 3255 | if( !emptyFlag ){ |
| 3256 | g.argc = 3; |
| 3257 |