├── 0001-builtin-fast-export-Add-importing-and-exporting-of.patch ├── README └── git-bzr /0001-builtin-fast-export-Add-importing-and-exporting-of.patch: -------------------------------------------------------------------------------- 1 | From df6a7ff7ac55d320afa1b8a59393122d6ca0f6c4 Mon Sep 17 00:00:00 2001 2 | From: Pieter de Bie 3 | Date: Wed, 11 Jun 2008 13:17:04 +0200 4 | Subject: [PATCH] builtin-fast-export: Add importing and exporting of revision marks 5 | 6 | This adds the --import-marks and --export-marks to fast-export. These import 7 | and export the marks used to for all revisions exported in a similar fashion 8 | to what fast-import does. The format is the same as fast-import, so you can 9 | create a bidirectional importer / exporter by using the same marks file on 10 | both sides. 11 | 12 | Signed-off-by: Pieter de Bie 13 | Signed-off-by: Junio C Hamano 14 | --- 15 | Documentation/git-fast-export.txt | 20 +++++++ 16 | builtin-fast-export.c | 99 ++++++++++++++++++++++++++++++++++-- 17 | t/t9301-fast-export.sh | 24 +++++++++ 18 | 3 files changed, 137 insertions(+), 6 deletions(-) 19 | 20 | diff --git a/Documentation/git-fast-export.txt b/Documentation/git-fast-export.txt 21 | index 332346c..277a547 100644 22 | --- a/Documentation/git-fast-export.txt 23 | +++ b/Documentation/git-fast-export.txt 24 | @@ -36,6 +36,26 @@ when encountering a signed tag. With 'strip', the tags will be made 25 | unsigned, with 'verbatim', they will be silently exported 26 | and with 'warn', they will be exported, but you will see a warning. 27 | 28 | +--export-marks=:: 29 | + Dumps the internal marks table to when complete. 30 | + Marks are written one per line as `:markid SHA-1`. Only marks 31 | + for revisions are dumped; marks for blobs are ignored. 32 | + Backends can use this file to validate imports after they 33 | + have been completed, or to save the marks table across 34 | + incremental runs. As is only opened and truncated 35 | + at completion, the same path can also be safely given to 36 | + \--import-marks. 37 | + 38 | +--import-marks=:: 39 | + Before processing any input, load the marks specified in 40 | + . The input file must exist, must be readable, and 41 | + must use the same format as produced by \--export-marks. 42 | ++ 43 | +Any commits that have already been marked will not be exported again. 44 | +If the backend uses a similar \--import-marks file, this allows for 45 | +incremental bidirectional exporting of the repository by keeping the 46 | +marks the same across runs. 47 | + 48 | 49 | EXAMPLES 50 | -------- 51 | diff --git a/builtin-fast-export.c b/builtin-fast-export.c 52 | index d0a462f..45786ef 100644 53 | --- a/builtin-fast-export.c 54 | +++ b/builtin-fast-export.c 55 | @@ -56,10 +56,24 @@ static int has_unshown_parent(struct commit *commit) 56 | } 57 | 58 | /* Since intptr_t is C99, we do not use it here */ 59 | -static void mark_object(struct object *object) 60 | +static inline uint32_t *mark_to_ptr(uint32_t mark) 61 | { 62 | - last_idnum++; 63 | - add_decoration(&idnums, object, ((uint32_t *)NULL) + last_idnum); 64 | + return ((uint32_t *)NULL) + mark; 65 | +} 66 | + 67 | +static inline uint32_t ptr_to_mark(void * mark) 68 | +{ 69 | + return (uint32_t *)mark - (uint32_t *)NULL; 70 | +} 71 | + 72 | +static inline void mark_object(struct object *object, uint32_t mark) 73 | +{ 74 | + add_decoration(&idnums, object, mark_to_ptr(mark)); 75 | +} 76 | + 77 | +static inline void mark_next_object(struct object *object) 78 | +{ 79 | + mark_object(object, ++last_idnum); 80 | } 81 | 82 | static int get_object_mark(struct object *object) 83 | @@ -67,7 +81,7 @@ static int get_object_mark(struct object *object) 84 | void *decoration = lookup_decoration(&idnums, object); 85 | if (!decoration) 86 | return 0; 87 | - return (uint32_t *)decoration - (uint32_t *)NULL; 88 | + return ptr_to_mark(decoration); 89 | } 90 | 91 | static void show_progress(void) 92 | @@ -100,7 +114,7 @@ static void handle_object(const unsigned char *sha1) 93 | if (!buf) 94 | die ("Could not read blob %s", sha1_to_hex(sha1)); 95 | 96 | - mark_object(object); 97 | + mark_next_object(object); 98 | 99 | printf("blob\nmark :%d\ndata %lu\n", last_idnum, size); 100 | if (size && fwrite(buf, size, 1, stdout) != 1) 101 | @@ -185,7 +199,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev) 102 | for (i = 0; i < diff_queued_diff.nr; i++) 103 | handle_object(diff_queued_diff.queue[i]->two->sha1); 104 | 105 | - mark_object(&commit->object); 106 | + mark_next_object(&commit->object); 107 | if (!is_encoding_utf8(encoding)) 108 | reencoded = reencode_string(message, "UTF-8", encoding); 109 | if (!commit->parents) 110 | @@ -354,18 +368,85 @@ static void handle_tags_and_duplicates(struct path_list *extra_refs) 111 | } 112 | } 113 | 114 | +static void export_marks(char *file) 115 | +{ 116 | + unsigned int i; 117 | + uint32_t mark; 118 | + struct object_decoration *deco = idnums.hash; 119 | + FILE *f; 120 | + 121 | + f = fopen(file, "w"); 122 | + if (!f) 123 | + error("Unable to open marks file %s for writing", file); 124 | + 125 | + for (i = 0; i < idnums.size; ++i) { 126 | + deco++; 127 | + if (deco && deco->base && deco->base->type == 1) { 128 | + mark = ptr_to_mark(deco->decoration); 129 | + fprintf(f, ":%u %s\n", mark, sha1_to_hex(deco->base->sha1)); 130 | + } 131 | + } 132 | + 133 | + if (ferror(f) || fclose(f)) 134 | + error("Unable to write marks file %s.", file); 135 | +} 136 | + 137 | +static void import_marks(char * input_file) 138 | +{ 139 | + char line[512]; 140 | + FILE *f = fopen(input_file, "r"); 141 | + if (!f) 142 | + die("cannot read %s: %s", input_file, strerror(errno)); 143 | + 144 | + while (fgets(line, sizeof(line), f)) { 145 | + uint32_t mark; 146 | + char *line_end, *mark_end; 147 | + unsigned char sha1[20]; 148 | + struct object *object; 149 | + 150 | + line_end = strchr(line, '\n'); 151 | + if (line[0] != ':' || !line_end) 152 | + die("corrupt mark line: %s", line); 153 | + *line_end = 0; 154 | + 155 | + mark = strtoumax(line + 1, &mark_end, 10); 156 | + if (!mark || mark_end == line + 1 157 | + || *mark_end != ' ' || get_sha1(mark_end + 1, sha1)) 158 | + die("corrupt mark line: %s", line); 159 | + 160 | + object = parse_object(sha1); 161 | + if (!object) 162 | + die ("Could not read blob %s", sha1_to_hex(sha1)); 163 | + 164 | + if (object->flags & SHOWN) 165 | + error("Object %s already has a mark", sha1); 166 | + 167 | + mark_object(object, mark); 168 | + if (last_idnum < mark) 169 | + last_idnum = mark; 170 | + 171 | + object->flags |= SHOWN; 172 | + } 173 | + fclose(f); 174 | +} 175 | + 176 | int cmd_fast_export(int argc, const char **argv, const char *prefix) 177 | { 178 | struct rev_info revs; 179 | struct object_array commits = { 0, 0, NULL }; 180 | struct path_list extra_refs = { NULL, 0, 0, 0 }; 181 | struct commit *commit; 182 | + char *export_filename = NULL, *import_filename = NULL; 183 | struct option options[] = { 184 | OPT_INTEGER(0, "progress", &progress, 185 | "show progress after objects"), 186 | OPT_CALLBACK(0, "signed-tags", &signed_tag_mode, "mode", 187 | "select handling of signed tags", 188 | parse_opt_signed_tag_mode), 189 | + OPT_STRING(0, "export-marks", &export_filename, "FILE", 190 | + "Dump marks to this file"), 191 | + OPT_STRING(0, "import-marks", &import_filename, "FILE", 192 | + "Import marks from this file"), 193 | OPT_END() 194 | }; 195 | 196 | @@ -378,6 +459,9 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) 197 | if (argc > 1) 198 | usage_with_options (fast_export_usage, options); 199 | 200 | + if (import_filename) 201 | + import_marks(import_filename); 202 | + 203 | get_tags_and_duplicates(&revs.pending, &extra_refs); 204 | 205 | if (prepare_revision_walk(&revs)) 206 | @@ -400,5 +484,8 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) 207 | 208 | handle_tags_and_duplicates(&extra_refs); 209 | 210 | + if (export_filename) 211 | + export_marks(export_filename); 212 | + 213 | return 0; 214 | } 215 | diff --git a/t/t9301-fast-export.sh b/t/t9301-fast-export.sh 216 | index f09bfb1..60b5ee3 100755 217 | --- a/t/t9301-fast-export.sh 218 | +++ b/t/t9301-fast-export.sh 219 | @@ -78,6 +78,30 @@ test_expect_success 'iso-8859-1' ' 220 | git cat-file commit i18n | grep "Áéí óú") 221 | 222 | ' 223 | +test_expect_success 'import/export-marks' ' 224 | + 225 | + git checkout -b marks master && 226 | + git fast-export --export-marks=tmp-marks HEAD && 227 | + test -s tmp-marks && 228 | + cp tmp-marks ~ && 229 | + test $(wc -l < tmp-marks) -eq 3 && 230 | + test $( 231 | + git fast-export --import-marks=tmp-marks\ 232 | + --export-marks=tmp-marks HEAD | 233 | + grep ^commit | 234 | + wc -l) \ 235 | + -eq 0 && 236 | + echo change > file && 237 | + git commit -m "last commit" file && 238 | + test $( 239 | + git fast-export --import-marks=tmp-marks \ 240 | + --export-marks=tmp-marks HEAD | 241 | + grep ^commit\ | 242 | + wc -l) \ 243 | + -eq 1 && 244 | + test $(wc -l < tmp-marks) -eq 4 245 | + 246 | +' 247 | 248 | cat > signed-tag-import << EOF 249 | tag sign-your-name 250 | -- 251 | 1.5.6.140.gaaae8 252 | 253 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | git-bzr: a bidirectional git - bazaar gateway 2 | --------- 3 | 4 | ## What does it do? 5 | 6 | This script allows you to add bazaar repositories as git branches in your git repository. After that, you can fetch the Bazaar repo, make some changes, and push it back into Bazaar. 7 | 8 | ## How does it work? 9 | 10 | An example session goes like this: 11 | 12 | $ git bzr add upstream ../bzr-branch 13 | $ git bzr fetch upstream 14 | $ git checkout -b local_branch bzr/upstream 15 | $ Hack hack, merge merge.... 16 | $ git bzr push upstream 17 | 18 | ## How should I install it? 19 | 20 | You need a new Git (v 1.6.0 or higher). If you have an older Git version, you can get git-bzr to run by applying the patch in this repository to your Git source code. 21 | 22 | Furthermore, you need the Bazaar fastimport plugin. It can be found at https://launchpad.net/bzr-fastimport. 23 | 24 | Finally, you need to install the git-bzr script, which is written in Ruby, somewhere. You will need to edit it a bit to make it work with your paths, as it is really unpolished and probably uses absolute pathnames. 25 | 26 | ## Why did you put this online? 27 | 28 | I'm not really interested in working on this anymore, as it fulfills my need. However, I can understand someone else might want to try the same, so I put the code online to avoid double work. 29 | 30 | NOTE: I really mean what I say here above -- I'm no longer interested in this program. Take it as it is, or look at one of the forks on github (there are two that have converted the script to bash, if you prefer that). 31 | 32 | ## How is it licensed? 33 | 34 | The Git patch is a derivative of the Git source and is thus licensed under Git's license (GPL v2.0 only). The Bazaar fastimport changes are covered under the same license as the rest of the fastimport plugin. The git-bzr script is licensed under the same license as Git. 35 | 36 | ## I have a question? 37 | 38 | You can try mailing me at frimmirf@gmail.com -------------------------------------------------------------------------------- /git-bzr: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | EXPORT_LOCATION = "~/.bazaar/plugins/fastimport/exporters/bzr-fast-export.py" 4 | PYTHON = ENV["PYTHON"] || "/opt/local/bin/python2.5" 5 | 6 | command = ARGV.shift 7 | commands = [:add, :push, :fetch, :pull] 8 | 9 | if !command || !commands.include?(command.to_sym) 10 | puts "Usage: git bzr [COMMAND] [OPTIONS]" 11 | puts "Commands: add, push, fetch, pull" 12 | exit 13 | end 14 | 15 | class BzrCommands 16 | 17 | def add(*args) 18 | name = args.shift 19 | location = args.shift 20 | unless name && location && args.empty? 21 | puts "Usage: git bzr add name location" 22 | exit 23 | end 24 | if `git remote show`.split("\n").include?(name) 25 | puts "There is already a remote with that name" 26 | exit 27 | end 28 | 29 | if `git config git-bzr.#{name}.url` != "" 30 | puts "There is alread a bazaar branch with that name" 31 | exit 32 | end 33 | 34 | if !File.exists?(File.join(location, ".bzr")) 35 | puts "Remote is not a bazaar repository" 36 | exit 37 | end 38 | 39 | `git config git-bzr.#{name}.location #{location}` 40 | puts "Bazaar branch #{name} added. You can fetch it with `git bzr fetch #{name}`" 41 | 42 | end 43 | 44 | def get_location(remote) 45 | l = `git config git-bzr.#{remote}.location`.strip 46 | if l == "" 47 | puts "Cannot find bazaar remote with name `#{remote}`." 48 | exit 49 | end 50 | l 51 | end 52 | 53 | def fetch(*args) 54 | remote = args.shift 55 | unless remote && args.empty? 56 | puts "Usage: git bzr fetch branchname" 57 | exit 58 | end 59 | location = get_location(remote) 60 | 61 | git_map = File.expand_path(File.join(git_dir, "bzr-git", "#{remote}-git-map")) 62 | bzr_map = File.expand_path(File.join(git_dir, "bzr-git", "#{remote}-bzr-map")) 63 | 64 | if !File.exists?(git_map) && !File.exists?(bzr_map) 65 | print "There doesn't seem to be an existing refmap. " 66 | puts "Doing an initial import" 67 | FileUtils.makedirs(File.dirname(git_map)) 68 | `(#{PYTHON} #{EXPORT_LOCATION} --export-marks=#{bzr_map} --git-branch=bzr/#{remote} #{location}) | (git fast-import --export-marks=#{git_map})` 69 | elsif File.exists?(git_map) && File.exists?(bzr_map) 70 | puts "Updating remote #{remote}" 71 | old_rev = `git rev-parse bzr/#{remote}` 72 | `(#{PYTHON} #{EXPORT_LOCATION} --import-marks=#{bzr_map} --export-marks=#{bzr_map} --git-branch=bzr/#{remote} #{location}) | (git fast-import --quiet --export-marks=#{git_map} --import-marks=#{git_map})` 73 | new_rev = `git rev-parse bzr/#{remote}` 74 | puts "Changes since last update:" 75 | puts `git shortlog #{old_rev.strip}..#{new_rev.strip}` 76 | else 77 | puts "One of the mapfiles is missing! Something went wrong!" 78 | end 79 | end 80 | 81 | def push(*args) 82 | remote = args.shift 83 | unless remote && args.empty? 84 | puts "Usage: git bzr push branchname" 85 | exit 86 | end 87 | location = get_location(remote) 88 | 89 | if `git rev-list --left-right HEAD...bzr/#{remote} | sed -n '/^>/ p'`.strip != "" 90 | puts "HEAD is not a strict child of #{remote}, cannot push. Merge first" 91 | exit 92 | end 93 | 94 | if `git rev-list --left-right HEAD...bzr/#{remote} | sed -n '/^ /dev/null` 116 | if $? != 0 117 | puts "Must be inside a git repository to work" 118 | exit 119 | end 120 | up = `git rev-parse --show-cdup`.strip 121 | up = "." if up == "" 122 | Dir.chdir(up) do 123 | __send__(cmd.to_s, *args) 124 | end 125 | end 126 | end 127 | 128 | 129 | BzrCommands.new.run(command, *ARGV) 130 | --------------------------------------------------------------------------------